This set of changes focuses on improvements to the nomcom portion of the datatracker.
These changes: Simplify the nomcom form for comments. Make it more obvious who receives mail when a comment is supplied. Fixes # 1849. Simplify the nomcom form for nominations. Provide a primary workflow where nominations choose an existing Person, and a secondary workflow for nominating new people. Allow nominees to add a comment when accepting or declining a nomination. Fixes #1845. Organize the list of nominees on the feedback page. Fixes #1786 and #1809. Simplify the mechanisms used to display feedback message counts. Regroup the feedback view to make it easier to see where to spend review effort. Fixes #1866. Capture when nomcom members last reviewed feedback for a given nominee. Add badges when new feedback is avaliable. Improve the layout of the feedback index page. Fixes #1850. Reorganize the tab navigation on the nomcom private pages. Made it more obvious when the chair is doing something that only the chair gets to see. Fixes #1788 and #1795. Regroup multiselect options to make classifying pending feedback simpler. Make the control larger and resizable. Fixes #1854. Simplify the chair's views for editing nominee records. Replace the merge nominee form with a request to the secretariat to merge Person records. Fixes #1847. Added merging nominees to the secretariat's persson merging script. Show information for concluded nomcoms. Close feedback and nomination for concluded nomcoms. Fixes #1856. Improve the questionnaire templates, reminding the nominee that receiving the questionnaire does not imply they have accepted a nomination. Fixes #1807. Remove the description field from Postion. Simplify the Position list and the Position edit form. Make the nomcom pages more self documenting. Add a page to help nomcom chiars through setting up a new nomcom. Fixes #1867 and #1768. Remove the type from the template pathname for the requirements templates. Make the requirements views work for both types plain and rst. Changed the default type for new nomcom requirement templates to rst. Remove 'incumbent' from the models. Fixes #1771. Adjust the models for Nominee and Nomination to better associate Nominee objects with Person objects. Remove BaseNomcomForm and the implementation of custom fieldsets. Replace the custom message framework with the django provided messages framework. Improve SearchablePersonField to show the primary email address for any search result where a name appears more than once. Add the use of factory-boy for generating test data. Normalize management of a test directory for test nomcom public keys. Significantly improve test coverage of the nomcom related code. Commit ready for merge. - Legacy-Id: 10629
This commit is contained in:
commit
c8bbfbad78
|
@ -12,11 +12,10 @@ import django
|
|||
django.setup()
|
||||
|
||||
import argparse
|
||||
import pprint
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.models import User
|
||||
from ietf.person.models import Person
|
||||
|
||||
from ietf.person.utils import merge_persons
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("source_id",type=int)
|
||||
parser.add_argument("target_id",type=int)
|
||||
|
@ -30,62 +29,4 @@ response = raw_input('Ok to continue y/n? ')
|
|||
if response.lower() != 'y':
|
||||
sys.exit()
|
||||
|
||||
# merge emails
|
||||
for email in source.email_set.all():
|
||||
print "Merging email: {}".format(email.address)
|
||||
email.person = target
|
||||
email.save()
|
||||
|
||||
# merge aliases
|
||||
target_aliases = [ a.name for a in target.alias_set.all() ]
|
||||
for alias in source.alias_set.all():
|
||||
if alias.name in target_aliases:
|
||||
alias.delete()
|
||||
else:
|
||||
print "Merging alias: {}".format(alias.name)
|
||||
alias.person = target
|
||||
alias.save()
|
||||
|
||||
# merge DocEvents
|
||||
for docevent in source.docevent_set.all():
|
||||
docevent.by = target
|
||||
docevent.save()
|
||||
|
||||
# merge SubmissionEvents
|
||||
for subevent in source.submissionevent_set.all():
|
||||
subevent.by = target
|
||||
subevent.save()
|
||||
|
||||
# merge Messages
|
||||
for message in source.message_set.all():
|
||||
message.by = target
|
||||
message.save()
|
||||
|
||||
# merge Constraints
|
||||
for constraint in source.constraint_set.all():
|
||||
constraint.person = target
|
||||
constraint.save()
|
||||
|
||||
# merge Roles
|
||||
for role in source.role_set.all():
|
||||
role.person = target
|
||||
role.save()
|
||||
|
||||
# check for any remaining relationships and delete if none
|
||||
objs = [source]
|
||||
opts = Person._meta
|
||||
user = User.objects.filter(is_superuser=True).first()
|
||||
admin_site = admin.site
|
||||
using = 'default'
|
||||
|
||||
deletable_objects, perms_needed, protected = admin.utils.get_deleted_objects(
|
||||
objs, opts, user, admin_site, using)
|
||||
|
||||
if len(deletable_objects) > 1:
|
||||
print "Not Deleting Person: {}({})".format(source.ascii,source.pk)
|
||||
print "Related objects remain:"
|
||||
pprint.pprint(deletable_objects[1])
|
||||
|
||||
else:
|
||||
print "Deleting Person: {}({})".format(source.ascii,source.pk)
|
||||
source.delete()
|
||||
merge_persons(source,target,sys.stdout)
|
||||
|
|
8
ietf/dbtemplate/factories.py
Normal file
8
ietf/dbtemplate/factories.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
import factory
|
||||
|
||||
from ietf.dbtemplate.models import DBTemplate
|
||||
|
||||
class DBTemplateFactory(factory.DjangoModelFactory):
|
||||
class Meta:
|
||||
model = DBTemplate
|
||||
|
|
@ -76,10 +76,10 @@ Questionnaire</field>
|
|||
<field to="group.group" name="group" rel="ManyToOneRel"><None></None></field>
|
||||
</object>
|
||||
<object pk="6" model="dbtemplate.dbtemplate">
|
||||
<field type="CharField" name="path">/nomcom/defaults/position/requirements.txt</field>
|
||||
<field type="CharField" name="path">/nomcom/defaults/position/requirements</field>
|
||||
<field type="CharField" name="title">Position requirements</field>
|
||||
<field type="TextField" name="variables">$position: Position</field>
|
||||
<field to="name.dbtemplatetypename" name="type" rel="ManyToOneRel">plain</field>
|
||||
<field to="name.dbtemplatetypename" name="type" rel="ManyToOneRel">rst</field>
|
||||
<field type="TextField" name="content">These are the requirements for the position $position:
|
||||
|
||||
Requirements.</field>
|
||||
|
|
40
ietf/dbtemplate/templates/dbtemplate/template_show.html
Normal file
40
ietf/dbtemplate/templates/dbtemplate/template_show.html
Normal file
|
@ -0,0 +1,40 @@
|
|||
{% extends "ietf.html" %}
|
||||
|
||||
{% load bootstrap3 %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Template: {{ template }}</h1>
|
||||
|
||||
<h2>Meta information</h2>
|
||||
<dl>
|
||||
<dt>Title</dt>
|
||||
<dd>{{ template.title }}</dt>
|
||||
<dt>Group</dt>
|
||||
<dd>{{ template.group }}</dd>
|
||||
<dt>Template type</dt>
|
||||
<dd>{{ template.type.name }}
|
||||
{% if template.type.slug == "rst" %}
|
||||
<p class="help-block">This template uses the syntax of reStructuredText. Get a quick reference at <a href="http://docutils.sourceforge.net/docs/user/rst/quickref.html">http://docutils.sourceforge.net/docs/user/rst/quickref.html</a>.</p>
|
||||
<p class="help-block">You can do variable interpolation with $varialbe if the template allows any variable.</p>
|
||||
{% endif %}
|
||||
{% if template.type.slug == "django" %}
|
||||
<p class="help-block">This template uses the syntax of the default django template framework. Get more info at <a href="https://docs.djangoproject.com/en/dev/topics/templates/">https://docs.djangoproject.com/en/dev/topics/templates/</a>.</p>
|
||||
<p class="help-block">You can do variable interpolation with the current django markup {{variable}} if the template allows any variable.</p>
|
||||
{% endif %}
|
||||
{% if template.type.slug == "plain" %}
|
||||
<p class="help-block">This template uses plain text, so no markup is used. You can do variable interpolation with $variable if the template allows any variable.</p>
|
||||
{% endif %}
|
||||
</dd>
|
||||
{% if template.variables %}
|
||||
<dt>Variables allowed in this template</dt>
|
||||
<dd>{{ template.variables|linebreaks }}</dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
|
||||
<h2>Template content</h2>
|
||||
|
||||
<div class = "panel panel-default">
|
||||
<p class='pasted'>{{ template.content }}</p>
|
||||
</div>
|
||||
|
||||
{% endblock content %}
|
|
@ -43,3 +43,19 @@ def template_edit(request, acronym, template_id, base_template='dbtemplate/templ
|
|||
}
|
||||
context.update(extra_context)
|
||||
return render(request, base_template, context)
|
||||
|
||||
def template_show(request, acronym, template_id, base_template='dbtemplate/template_edit.html', extra_context=None):
|
||||
group = get_object_or_404(Group, acronym=acronym)
|
||||
chairs = group.role_set.filter(name__slug='chair')
|
||||
extra_context = extra_context or {}
|
||||
|
||||
if not has_role(request.user, "Secretariat") and not chairs.filter(person__user=request.user).count():
|
||||
return HttpResponseForbidden("You are not authorized to access this view")
|
||||
|
||||
template = get_object_or_404(DBTemplate, id=template_id, group=group)
|
||||
|
||||
context = {'template': template,
|
||||
'group': group,
|
||||
}
|
||||
context.update(extra_context)
|
||||
return render(request, base_template, context)
|
||||
|
|
|
@ -1302,6 +1302,7 @@ class ChangeReplacesTests(TestCase):
|
|||
expires=datetime.datetime.now() + datetime.timedelta(days=settings.INTERNET_DRAFT_DAYS_TO_EXPIRE),
|
||||
group=mars_wg,
|
||||
)
|
||||
self.basea.documentauthor_set.create(author=Email.objects.create(address="basea_author@example.com"),order=1)
|
||||
|
||||
self.baseb = Document.objects.create(
|
||||
name="draft-test-base-b",
|
||||
|
@ -1312,6 +1313,7 @@ class ChangeReplacesTests(TestCase):
|
|||
expires=datetime.datetime.now() - datetime.timedelta(days = 365 - settings.INTERNET_DRAFT_DAYS_TO_EXPIRE),
|
||||
group=mars_wg,
|
||||
)
|
||||
self.baseb.documentauthor_set.create(author=Email.objects.create(address="baseb_author@example.com"),order=1)
|
||||
|
||||
self.replacea = Document.objects.create(
|
||||
name="draft-test-replace-a",
|
||||
|
@ -1322,6 +1324,7 @@ class ChangeReplacesTests(TestCase):
|
|||
expires=datetime.datetime.now() + datetime.timedelta(days = settings.INTERNET_DRAFT_DAYS_TO_EXPIRE),
|
||||
group=mars_wg,
|
||||
)
|
||||
self.replacea.documentauthor_set.create(author=Email.objects.create(address="replacea_author@example.com"),order=1)
|
||||
|
||||
self.replaceboth = Document.objects.create(
|
||||
name="draft-test-replace-both",
|
||||
|
@ -1332,6 +1335,7 @@ class ChangeReplacesTests(TestCase):
|
|||
expires=datetime.datetime.now() + datetime.timedelta(days = settings.INTERNET_DRAFT_DAYS_TO_EXPIRE),
|
||||
group=mars_wg,
|
||||
)
|
||||
self.replaceboth.documentauthor_set.create(author=Email.objects.create(address="replaceboth_author@example.com"),order=1)
|
||||
|
||||
self.basea.set_state(State.objects.get(used=True, type="draft", slug="active"))
|
||||
self.baseb.set_state(State.objects.get(used=True, type="draft", slug="expired"))
|
||||
|
@ -1366,8 +1370,8 @@ class ChangeReplacesTests(TestCase):
|
|||
self.assertTrue(not RelatedDocument.objects.filter(relationship='possibly-replaces', source=self.replacea))
|
||||
self.assertEqual(len(outbox), 1)
|
||||
self.assertTrue('replacement status updated' in outbox[-1]['Subject'])
|
||||
self.assertTrue('base-a@' in outbox[-1]['To'])
|
||||
self.assertTrue('replace-a@' in outbox[-1]['To'])
|
||||
self.assertTrue('replacea_author@' in outbox[-1]['To'])
|
||||
self.assertTrue('basea_author@' in outbox[-1]['To'])
|
||||
|
||||
empty_outbox()
|
||||
# Post that says replaceboth replaces both base a and base b
|
||||
|
@ -1378,9 +1382,9 @@ class ChangeReplacesTests(TestCase):
|
|||
self.assertEqual(Document.objects.get(name='draft-test-base-a').get_state().slug,'repl')
|
||||
self.assertEqual(Document.objects.get(name='draft-test-base-b').get_state().slug,'repl')
|
||||
self.assertEqual(len(outbox), 1)
|
||||
self.assertTrue('base-a@' in outbox[-1]['To'])
|
||||
self.assertTrue('base-b@' in outbox[-1]['To'])
|
||||
self.assertTrue('replace-both@' in outbox[-1]['To'])
|
||||
self.assertTrue('basea_author@' in outbox[-1]['To'])
|
||||
self.assertTrue('baseb_author@' in outbox[-1]['To'])
|
||||
self.assertTrue('replaceboth_author@' in outbox[-1]['To'])
|
||||
|
||||
# Post that undoes replaceboth
|
||||
empty_outbox()
|
||||
|
@ -1389,9 +1393,9 @@ class ChangeReplacesTests(TestCase):
|
|||
self.assertEqual(Document.objects.get(name='draft-test-base-a').get_state().slug,'repl') # Because A is still also replaced by replacea
|
||||
self.assertEqual(Document.objects.get(name='draft-test-base-b').get_state().slug,'expired')
|
||||
self.assertEqual(len(outbox), 1)
|
||||
self.assertTrue('base-a@' in outbox[-1]['To'])
|
||||
self.assertTrue('base-b@' in outbox[-1]['To'])
|
||||
self.assertTrue('replace-both@' in outbox[-1]['To'])
|
||||
self.assertTrue('basea_author@' in outbox[-1]['To'])
|
||||
self.assertTrue('baseb_author@' in outbox[-1]['To'])
|
||||
self.assertTrue('replaceboth_author@' in outbox[-1]['To'])
|
||||
|
||||
# Post that undoes replacea
|
||||
empty_outbox()
|
||||
|
@ -1399,8 +1403,8 @@ class ChangeReplacesTests(TestCase):
|
|||
r = self.client.post(url, dict(replaces=""))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
self.assertEqual(Document.objects.get(name='draft-test-base-a').get_state().slug,'active')
|
||||
self.assertTrue('base-a@' in outbox[-1]['To'])
|
||||
self.assertTrue('replace-a@' in outbox[-1]['To'])
|
||||
self.assertTrue('basea_author@' in outbox[-1]['To'])
|
||||
self.assertTrue('replacea_author@' in outbox[-1]['To'])
|
||||
|
||||
|
||||
def test_review_possibly_replaces(self):
|
||||
|
|
|
@ -20,6 +20,8 @@ from ietf.utils import draft, markup_txt
|
|||
from ietf.utils.mail import send_mail
|
||||
from ietf.mailtrigger.utils import gather_address_lists
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
#TODO FIXME - it would be better if this lived in ietf/doc/mails.py, but there's
|
||||
# an import order issue to work out.
|
||||
def email_update_telechat(request, doc, text):
|
||||
|
|
10
ietf/group/factories.py
Normal file
10
ietf/group/factories.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
import factory
|
||||
|
||||
from ietf.group.models import Group
|
||||
|
||||
class GroupFactory(factory.DjangoModelFactory):
|
||||
class Meta:
|
||||
model = Group
|
||||
|
||||
name = factory.Faker('sentence',nb_words=6)
|
||||
acronym = factory.Faker('word')
|
|
@ -64,9 +64,9 @@ def has_role(user, role_names, *args, **kwargs):
|
|||
"RG Secretary": Q(person=person,name="secr", group__type="rg", group__state__in=["active","proposed"]),
|
||||
"AG Secretary": Q(person=person,name="secr", group__type="ag", group__state__in=["active"]),
|
||||
"Team Chair": Q(person=person,name="chair", group__type="team", group__state="active"),
|
||||
"Nomcom Chair": Q(person=person, name="chair", group__type="nomcom", group__state="active", group__acronym__icontains=kwargs.get('year', '0000')),
|
||||
"Nomcom Advisor": Q(person=person, name="advisor", group__type="nomcom", group__state="active", group__acronym__icontains=kwargs.get('year', '0000')),
|
||||
"Nomcom": Q(person=person, group__type="nomcom", group__state="active", group__acronym__icontains=kwargs.get('year', '0000')),
|
||||
"Nomcom Chair": Q(person=person, name="chair", group__type="nomcom", group__acronym__icontains=kwargs.get('year', '0000')),
|
||||
"Nomcom Advisor": Q(person=person, name="advisor", group__type="nomcom", group__acronym__icontains=kwargs.get('year', '0000')),
|
||||
"Nomcom": Q(person=person, group__type="nomcom", group__acronym__icontains=kwargs.get('year', '0000')),
|
||||
"Liaison Manager": Q(person=person,name="liaiman",group__type="sdo",group__state="active", ),
|
||||
"Authorized Individual": Q(person=person,name="auth",group__type="sdo",group__state="active", ),
|
||||
}
|
||||
|
|
28
ietf/mailtrigger/migrations/0003_merge_request_trigger.py
Normal file
28
ietf/mailtrigger/migrations/0003_merge_request_trigger.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
def forward(apps, schema_editor):
|
||||
|
||||
Recipient=apps.get_model('mailtrigger','Recipient')
|
||||
MailTrigger=apps.get_model('mailtrigger','MailTrigger')
|
||||
|
||||
m = MailTrigger.objects.create(
|
||||
slug='person_merge_requested',
|
||||
desc="Recipients for a message requesting that duplicated Person records be merged ")
|
||||
m.to = Recipient.objects.filter(slug__in=['ietf_secretariat', ])
|
||||
|
||||
def reverse(apps, schema_editor):
|
||||
MailTrigger=apps.get_model('mailtrigger','MailTrigger')
|
||||
MailTrigger.objects.filter(slug='person_merge_requested').delete()
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mailtrigger', '0002_auto_20150809_1314'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(forward, reverse)
|
||||
]
|
|
@ -648,6 +648,7 @@
|
|||
{
|
||||
"fields": {
|
||||
"order": 0,
|
||||
"prefix": "charter",
|
||||
"used": true,
|
||||
"name": "Charter",
|
||||
"desc": ""
|
||||
|
@ -658,6 +659,7 @@
|
|||
{
|
||||
"fields": {
|
||||
"order": 0,
|
||||
"prefix": "agenda",
|
||||
"used": true,
|
||||
"name": "Agenda",
|
||||
"desc": ""
|
||||
|
@ -668,6 +670,7 @@
|
|||
{
|
||||
"fields": {
|
||||
"order": 0,
|
||||
"prefix": "minutes",
|
||||
"used": true,
|
||||
"name": "Minutes",
|
||||
"desc": ""
|
||||
|
@ -678,6 +681,7 @@
|
|||
{
|
||||
"fields": {
|
||||
"order": 0,
|
||||
"prefix": "slides",
|
||||
"used": true,
|
||||
"name": "Slides",
|
||||
"desc": ""
|
||||
|
@ -688,6 +692,7 @@
|
|||
{
|
||||
"fields": {
|
||||
"order": 0,
|
||||
"prefix": "draft",
|
||||
"used": true,
|
||||
"name": "Draft",
|
||||
"desc": ""
|
||||
|
@ -698,6 +703,7 @@
|
|||
{
|
||||
"fields": {
|
||||
"order": 0,
|
||||
"prefix": "liai-att",
|
||||
"used": true,
|
||||
"name": "Liaison Attachment",
|
||||
"desc": ""
|
||||
|
@ -708,6 +714,7 @@
|
|||
{
|
||||
"fields": {
|
||||
"order": 0,
|
||||
"prefix": "conflict-review",
|
||||
"used": true,
|
||||
"name": "Conflict Review",
|
||||
"desc": ""
|
||||
|
@ -718,6 +725,7 @@
|
|||
{
|
||||
"fields": {
|
||||
"order": 0,
|
||||
"prefix": "status-change",
|
||||
"used": true,
|
||||
"name": "Status Change",
|
||||
"desc": ""
|
||||
|
@ -728,6 +736,7 @@
|
|||
{
|
||||
"fields": {
|
||||
"order": 0,
|
||||
"prefix": "",
|
||||
"used": false,
|
||||
"name": "Shepherd's writeup",
|
||||
"desc": ""
|
||||
|
@ -738,6 +747,7 @@
|
|||
{
|
||||
"fields": {
|
||||
"order": 0,
|
||||
"prefix": "",
|
||||
"used": false,
|
||||
"name": "Liaison",
|
||||
"desc": ""
|
||||
|
@ -748,6 +758,7 @@
|
|||
{
|
||||
"fields": {
|
||||
"order": 0,
|
||||
"prefix": "recording",
|
||||
"used": true,
|
||||
"name": "Recording",
|
||||
"desc": ""
|
||||
|
@ -758,6 +769,7 @@
|
|||
{
|
||||
"fields": {
|
||||
"order": 0,
|
||||
"prefix": "bluesheets",
|
||||
"used": true,
|
||||
"name": "Bluesheets",
|
||||
"desc": ""
|
||||
|
@ -4551,6 +4563,14 @@
|
|||
"model": "mailtrigger.recipient",
|
||||
"pk": "doc_authors"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"template": "{{doc.author_list}}",
|
||||
"desc": "The authors of the document, without using the draft aliases"
|
||||
},
|
||||
"model": "mailtrigger.recipient",
|
||||
"pk": "doc_authors_expanded"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"template": null,
|
||||
|
@ -4711,6 +4731,14 @@
|
|||
"model": "mailtrigger.recipient",
|
||||
"pk": "iana_last_call"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"template": "<i-d-announce@ietf.org>",
|
||||
"desc": "The I-D-Announce Email List"
|
||||
},
|
||||
"model": "mailtrigger.recipient",
|
||||
"pk": "id_announce"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"template": "The IESG <iesg@ietf.org>",
|
||||
|
@ -5316,11 +5344,7 @@
|
|||
"fields": {
|
||||
"cc": [],
|
||||
"to": [
|
||||
"doc_authors",
|
||||
"doc_group_chairs",
|
||||
"doc_group_responsible_directors",
|
||||
"doc_notify",
|
||||
"doc_shepherd"
|
||||
"doc_authors_expanded"
|
||||
],
|
||||
"desc": "Recipients when what a document replaces or is replaced by changes"
|
||||
},
|
||||
|
@ -5708,6 +5732,17 @@
|
|||
"model": "mailtrigger.mailtrigger",
|
||||
"pk": "nomination_received"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"cc": [],
|
||||
"to": [
|
||||
"ietf_secretariat"
|
||||
],
|
||||
"desc": "Recipients for a message requesting that duplicated Person records be merged "
|
||||
},
|
||||
"model": "mailtrigger.mailtrigger",
|
||||
"pk": "person_merge_requested"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"cc": [
|
||||
|
@ -5853,7 +5888,7 @@
|
|||
"submission_group_mail_list"
|
||||
],
|
||||
"to": [
|
||||
"ietf_announce"
|
||||
"id_announce"
|
||||
],
|
||||
"desc": "Recipients for the announcement of a successfully submitted draft"
|
||||
},
|
||||
|
|
|
@ -23,7 +23,7 @@ class NomineePositionAdmin(admin.ModelAdmin):
|
|||
|
||||
|
||||
class PositionAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'nomcom', 'is_open', 'incumbent')
|
||||
list_display = ('name', 'nomcom', 'is_open')
|
||||
list_filter = ('nomcom',)
|
||||
|
||||
|
||||
|
|
147
ietf/nomcom/factories.py
Normal file
147
ietf/nomcom/factories.py
Normal file
|
@ -0,0 +1,147 @@
|
|||
import factory
|
||||
import random
|
||||
|
||||
from ietf.nomcom.models import NomCom, Position, Feedback, Nominee, NomineePosition
|
||||
from ietf.group.factories import GroupFactory
|
||||
from ietf.person.factories import PersonFactory
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
cert = '''-----BEGIN CERTIFICATE-----
|
||||
MIIDHjCCAgagAwIBAgIJAKDCCjbQboJzMA0GCSqGSIb3DQEBCwUAMBMxETAPBgNV
|
||||
BAMMCE5vbUNvbTE1MB4XDTE0MDQwNDIxMTQxNFoXDTE2MDQwMzIxMTQxNFowEzER
|
||||
MA8GA1UEAwwITm9tQ29tMTUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
|
||||
AQC2QXCsAitYSOgPYor77zQnEeHuVqlcuhpH1wpKB+N6WcScA5N3AnX9uZEFOt6M
|
||||
cJ+MCiHECdqDlH6npQTJlpCpIVgAD4B6xzjRBRww8d3lClA/kKwsKzuX93RS0Uv3
|
||||
0hAD6q9wjqK/m6vR5Y1SsvJYV0y+Yu5j9xUEsojMH7O3NlXWAYOb6oH+f/X7PX27
|
||||
IhtiCwfICMmVWh/hKeXuFx6HSOcH3gZ6Tlk1llfDbE/ArpsZ6JmnLn73+64yqIoO
|
||||
ZOc4JJUPrdsmbNwXoxQSQhrpwjN8NpSkQaJbHGB3G+OWvP4fpqcweFHxlEq1Hhef
|
||||
uR9E6jc3qwxVQfwjbcq6N/4JAgMBAAGjdTBzMB0GA1UdDgQWBBTJow+TJynRWsTQ
|
||||
LzoS861FGb/rxDAOBgNVHQ8BAf8EBAMCBLAwDwYDVR0TAQH/BAUwAwEB/zAcBgNV
|
||||
HREEFTATgRFub21jb20xNUBpZXRmLm9yZzATBgNVHSUEDDAKBggrBgEFBQcDBDAN
|
||||
BgkqhkiG9w0BAQsFAAOCAQEAJwLapB9u5N3iK6SCTqh+PVkigZeB2YMVBW8WA3Ut
|
||||
iRPBj+jHWOpF5pzZHTOcNaAxDEG9lyIlcWqc93A24K/Gen11Tx0hO4FAPOG0+PP8
|
||||
4lx7F6xeeyUNR44pInrB93G2q0jl+3wjZH8uhBKlGji4UTMpDPpEl6uiyQCbkMMm
|
||||
Vr7HZH5Dv/lsjGHHf8uJO7+mcMh+tqxLn3DzPrm61OfeWdkoVX2pTz0imRQ3Es+8
|
||||
I7zNMk+fNNaEEyPnEyHfuWq0uD/qKeP27NZIoINy6E3INQ5QaE2uc1nQULg5y7uJ
|
||||
toX3j+FUe2UiUak3ACXdrOPSsFP0KRrFwuMnuHHXkGj/Uw==
|
||||
-----END CERTIFICATE-----
|
||||
'''
|
||||
|
||||
key = '''-----BEGIN PRIVATE KEY-----
|
||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC2QXCsAitYSOgP
|
||||
Yor77zQnEeHuVqlcuhpH1wpKB+N6WcScA5N3AnX9uZEFOt6McJ+MCiHECdqDlH6n
|
||||
pQTJlpCpIVgAD4B6xzjRBRww8d3lClA/kKwsKzuX93RS0Uv30hAD6q9wjqK/m6vR
|
||||
5Y1SsvJYV0y+Yu5j9xUEsojMH7O3NlXWAYOb6oH+f/X7PX27IhtiCwfICMmVWh/h
|
||||
KeXuFx6HSOcH3gZ6Tlk1llfDbE/ArpsZ6JmnLn73+64yqIoOZOc4JJUPrdsmbNwX
|
||||
oxQSQhrpwjN8NpSkQaJbHGB3G+OWvP4fpqcweFHxlEq1HhefuR9E6jc3qwxVQfwj
|
||||
bcq6N/4JAgMBAAECggEAb5SS4YwWc193S2v+QQ2KdVz6YEuINq/tRQw/TWGVACQT
|
||||
PZzm3FaSXDsOsRAAjiSpWTgewgFyWVpBTGu4CZ73g8RZNvhGpWRwwW8KemCpg/8T
|
||||
cEcnUYdKXdhuzAE9LETb7znwHM4Gj55DzCZopjfOLQ2Ne4XgAy2THaQcIjRKd6Bw
|
||||
3mteJ2ityDj3iFN7cq9ntDzp+2BqLOi7AZmLntmUZxtkPCT6k5/dcKFYQW9Eb3bt
|
||||
MON+BIYVzqhAijkP/cAWmbgZAP9EFng5PpE1lc/shl0W8eX4yvjNoMPRq3wphS4j
|
||||
L16VncUeDep3vR0CECx7gnTfR0uCDEgKow50pzGQAQKBgQDaQWwK/o39zI3lCGzy
|
||||
oSNJRNQJ/iZBkbbwpCCaka7VnBfd0ZH54VEWL3oMTkkWRSZtjsPAqT+ndwZitm0D
|
||||
Kww9FUDMP7j/tMOwAUHYfjYFqFTn6ipkBuby9tbZtL7lgJO6Iu2Qk3afqADD0kcP
|
||||
zRLxcYSLjrmp9NyUlNnpswR4CQKBgQDVxjwG/orCmiuyA1Bu4u1hdUD0w9CKnyjp
|
||||
VTbkv8lxk5V3pYzms2Awb0X43W2OioYGBk5yw+9GCF//xCrfbGV7BLZnDTGShjkJ
|
||||
8oTpLPGBsDSfaKVXE3Hko4LVLBMQIm0tDyuPD1Naia7ZknYn906skonEG8WgHUyp
|
||||
c/BgkvzWAQKBgBdojuL6/FWtO8bFyZGYUMWJ+Uf9FzNPIpTatZh+aYcFj9W9pW9s
|
||||
iBreCrQJLXOTBRUZC8u9G1Olw2yQ7k45rr1aazG83+WlCJv29o32s2qV7E1XYyaJ
|
||||
SvniGZcN+K96w91h46Lu/fkPts1J309FinOU3kdtjmI5HfNdp6WWCrOpAoGBAMjc
|
||||
TEaeIK8cwPWwG4E1A6pQy8mvu2Ckj4I+KSfh9FsdOpGDIdMas8SOqQZet7P5AFjk
|
||||
0A0RgN8iu2DMZyQq62cdVG2bffqY1zs7fhrBueILOEaXwtMAWEFmSWYW1YqRbleq
|
||||
K1luIvms6HdSIGcI/gk0XvG+zn/VR9ToNPHo6lwBAoGBAIrYGYPf+cjZ1V/tNqnL
|
||||
IecEZb4Gkp1hVhOpNT4U+T2LROxrZtFxxsw2vuIRa5a5FtMbDq9Xyhkm0QppliBd
|
||||
KQ38jTT0EaD2+vstTqL8vxupo25RQWV1XsmLL4pLbKnm2HnnwB3vEtsiokWKW0q0
|
||||
Tdb0MiLc+r/zvx8oXtgDjDUa
|
||||
-----END PRIVATE KEY-----
|
||||
'''
|
||||
|
||||
def provide_private_key_to_test_client(testcase):
|
||||
session = testcase.client.session
|
||||
session['NOMCOM_PRIVATE_KEY_%s'%testcase.nc.year()] = key
|
||||
session.save()
|
||||
|
||||
def nomcom_kwargs_for_year(year=None, *args, **kwargs):
|
||||
if not year:
|
||||
year = random.randint(1980,2100)
|
||||
if 'group__state_id' not in kwargs:
|
||||
kwargs['group__state_id']='active'
|
||||
if 'group__acronym' not in kwargs:
|
||||
kwargs['group__acronym'] = 'nomcom%d'%year
|
||||
if 'group__name' not in kwargs:
|
||||
kwargs['group__name'] = 'TEST VERSION of IAB/IESG Nominating Committee %d/%d'%(year,year+1)
|
||||
return kwargs
|
||||
|
||||
|
||||
class NomComFactory(factory.DjangoModelFactory):
|
||||
class Meta:
|
||||
model = NomCom
|
||||
|
||||
group = factory.SubFactory(GroupFactory,type_id='nomcom')
|
||||
|
||||
public_key = factory.django.FileField(data=cert)
|
||||
|
||||
@factory.post_generation
|
||||
def populate_positions(self, create, extracted, **kwargs):
|
||||
'''
|
||||
Create a set of nominees and positions unless NomcomFactory is called
|
||||
with populate_positions=False
|
||||
'''
|
||||
if extracted is None:
|
||||
extracted = True
|
||||
if create and extracted:
|
||||
nominees = [NomineeFactory(nomcom=self) for i in range(4)]
|
||||
positions = [PositionFactory(nomcom=self) for i in range(3)]
|
||||
|
||||
def npc(position,nominee,state_id):
|
||||
return NomineePosition.objects.create(position=position,
|
||||
nominee=nominee,
|
||||
state_id=state_id)
|
||||
# This gives us positions with 0, 1 and 2 nominees, and
|
||||
# one person who's been nominated for more than one position
|
||||
npc(positions[0],nominees[0],'accepted')
|
||||
npc(positions[1],nominees[0],'accepted')
|
||||
npc(positions[1],nominees[1],'accepted')
|
||||
npc(positions[0],nominees[2],'pending')
|
||||
npc(positions[0],nominees[3],'declined')
|
||||
|
||||
@factory.post_generation
|
||||
def populate_personnel(self, create, extracted, **kwargs):
|
||||
'''
|
||||
Create a default set of role holders, unless the factory is called
|
||||
with populate_personnel=False
|
||||
'''
|
||||
if extracted is None:
|
||||
extracted = True
|
||||
if create and extracted:
|
||||
#roles= ['chair', 'advisor'] + ['member']*10
|
||||
roles = ['chair', 'advisor', 'member']
|
||||
for role in roles:
|
||||
p = PersonFactory()
|
||||
self.group.role_set.create(name_id=role,person=p,email=p.email_set.first())
|
||||
|
||||
class PositionFactory(factory.DjangoModelFactory):
|
||||
class Meta:
|
||||
model = Position
|
||||
|
||||
name = factory.Faker('sentence',nb_words=10)
|
||||
is_open = True
|
||||
|
||||
class NomineeFactory(factory.DjangoModelFactory):
|
||||
class Meta:
|
||||
model = Nominee
|
||||
|
||||
nomcom = factory.SubFactory(NomComFactory)
|
||||
person = factory.SubFactory(PersonFactory)
|
||||
email = factory.LazyAttribute(lambda n: n.person.email())
|
||||
|
||||
class FeedbackFactory(factory.DjangoModelFactory):
|
||||
class Meta:
|
||||
model = Feedback
|
||||
|
||||
nomcom = factory.SubFactory(NomComFactory)
|
||||
subject = factory.Faker('sentence')
|
||||
comments = factory.Faker('paragraph')
|
||||
type_id = 'comment'
|
|
@ -2,26 +2,30 @@ from django.conf import settings
|
|||
from django import forms
|
||||
from django.contrib.formtools.preview import FormPreview, AUTO_ID
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.shortcuts import render_to_response
|
||||
from django.template.context import RequestContext
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.html import mark_safe
|
||||
|
||||
from ietf.dbtemplate.forms import DBTemplateForm
|
||||
from ietf.group.models import Group, Role
|
||||
from ietf.ietfauth.utils import role_required
|
||||
from ietf.name.models import RoleName, FeedbackTypeName, NomineePositionStateName
|
||||
from ietf.name.models import RoleName, FeedbackTypeName
|
||||
from ietf.nomcom.models import ( NomCom, Nomination, Nominee, NomineePosition,
|
||||
Position, Feedback, ReminderDates )
|
||||
from ietf.nomcom.utils import (NOMINATION_RECEIPT_TEMPLATE, FEEDBACK_RECEIPT_TEMPLATE,
|
||||
get_user_email, validate_private_key, validate_public_key,
|
||||
get_or_create_nominee, create_feedback_email)
|
||||
make_nomineeposition, make_nomineeposition_for_newperson,
|
||||
create_feedback_email)
|
||||
from ietf.person.models import Email
|
||||
from ietf.person.fields import SearchableEmailField
|
||||
from ietf.person.fields import SearchableEmailField, SearchablePersonField, SearchablePersonsField
|
||||
from ietf.utils.fields import MultiEmailField
|
||||
from ietf.utils.mail import send_mail
|
||||
from ietf.mailtrigger.utils import gather_address_lists
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
|
||||
ROLODEX_URL = getattr(settings, 'ROLODEX_URL', None)
|
||||
|
||||
|
@ -40,9 +44,15 @@ class PositionNomineeField(forms.ChoiceField):
|
|||
positions = Position.objects.get_by_nomcom(self.nomcom).opened().order_by('name')
|
||||
results = []
|
||||
for position in positions:
|
||||
nominees = [('%s_%s' % (position.id, i.id), unicode(i)) for i in Nominee.objects.get_by_nomcom(self.nomcom).not_duplicated().filter(nominee_position=position).select_related("email", "email__person")]
|
||||
accepted_nominees = [np.nominee for np in NomineePosition.objects.filter(position=position,state='accepted').exclude(nominee__duplicated__isnull=False)]
|
||||
nominees = [('%s_%s' % (position.id, i.id), unicode(i)) for i in accepted_nominees]
|
||||
if nominees:
|
||||
results.append((position.name, nominees))
|
||||
results.append((position.name+" (Accepted)", nominees))
|
||||
for position in positions:
|
||||
other_nominees = [np.nominee for np in NomineePosition.objects.filter(position=position).exclude(state='accepted').exclude(nominee__duplicated__isnull=False)]
|
||||
nominees = [('%s_%s' % (position.id, i.id), unicode(i)) for i in other_nominees]
|
||||
if nominees:
|
||||
results.append((position.name+" (Declined or Pending)", nominees))
|
||||
kwargs['choices'] = results
|
||||
super(PositionNomineeField, self).__init__(*args, **kwargs)
|
||||
|
||||
|
@ -83,35 +93,10 @@ class MultiplePositionNomineeField(forms.MultipleChoiceField, PositionNomineeFie
|
|||
return result
|
||||
|
||||
|
||||
class BaseNomcomForm(object):
|
||||
def __unicode__(self):
|
||||
return self.as_div()
|
||||
|
||||
def as_div(self):
|
||||
return render_to_string('nomcom/nomcomform.html', {'form': self})
|
||||
|
||||
def get_fieldsets(self):
|
||||
if not self.fieldsets:
|
||||
yield dict(name=None, fields=self)
|
||||
else:
|
||||
for fieldset, fields in self.fieldsets:
|
||||
fieldset_dict = dict(name=fieldset, fields=[])
|
||||
for field_name in fields:
|
||||
if field_name in self.fields:
|
||||
fieldset_dict['fields'].append(self[field_name])
|
||||
if not fieldset_dict['fields']:
|
||||
# if there is no fields in this fieldset, we continue to next fieldset
|
||||
continue
|
||||
yield fieldset_dict
|
||||
|
||||
|
||||
class EditMembersForm(BaseNomcomForm, forms.Form):
|
||||
class EditMembersForm(forms.Form):
|
||||
|
||||
members = MultiEmailField(label="Members email", required=False, widget=forms.Textarea)
|
||||
|
||||
fieldsets = [('Members', ('members',))]
|
||||
|
||||
|
||||
class EditMembersFormPreview(FormPreview):
|
||||
form_template = 'nomcom/edit_members.html'
|
||||
preview_template = 'nomcom/edit_members_preview.html'
|
||||
|
@ -208,10 +193,8 @@ class EditMembersFormPreview(FormPreview):
|
|||
return redirect('nomcom_edit_members', year=self.year)
|
||||
|
||||
|
||||
class EditNomcomForm(BaseNomcomForm, forms.ModelForm):
|
||||
class EditNomcomForm(forms.ModelForm):
|
||||
|
||||
fieldsets = [('Edit nomcom settings', ('public_key', 'initial_text',
|
||||
'send_questionnaire', 'reminder_interval'))]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(EditNomcomForm, self).__init__(*args, **kwargs)
|
||||
|
@ -238,100 +221,42 @@ class EditNomcomForm(BaseNomcomForm, forms.ModelForm):
|
|||
raise forms.ValidationError('Invalid public key. Error was: %s' % error)
|
||||
|
||||
|
||||
class MergeForm(BaseNomcomForm, forms.Form):
|
||||
class MergeForm(forms.Form):
|
||||
|
||||
secondary_emails = MultiEmailField(label="Secondary email addresses",
|
||||
help_text="Provide a comma separated list of email addresses. Nominations already received with any of these email address will be moved to show under the primary address.", widget=forms.Textarea)
|
||||
primary_email = forms.EmailField(label="Primary email address",
|
||||
widget=forms.TextInput(attrs={'size': '40'}))
|
||||
|
||||
fieldsets = [('Emails', ('primary_email', 'secondary_emails'))]
|
||||
primary_person = SearchablePersonField(help_text="Select the person you want the datatracker to keep")
|
||||
duplicate_persons = SearchablePersonsField(help_text="Select all the duplicates that should be merged into the primary person record")
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.nomcom = kwargs.pop('nomcom', None)
|
||||
super(MergeForm, self).__init__(*args, **kwargs)
|
||||
|
||||
def clean_primary_email(self):
|
||||
email = self.cleaned_data['primary_email']
|
||||
nominees = Nominee.objects.get_by_nomcom(self.nomcom).not_duplicated().filter(email__address=email)
|
||||
if not nominees:
|
||||
msg = "No nominee with this email exists"
|
||||
self._errors["primary_email"] = self.error_class([msg])
|
||||
|
||||
return email
|
||||
|
||||
def clean_secondary_emails(self):
|
||||
emails = self.cleaned_data['secondary_emails']
|
||||
for email in emails:
|
||||
nominees = Nominee.objects.get_by_nomcom(self.nomcom).not_duplicated().filter(email__address=email)
|
||||
if not nominees:
|
||||
msg = "No nominee with email %s exists" % email
|
||||
self._errors["primary_email"] = self.error_class([msg])
|
||||
break
|
||||
|
||||
return emails
|
||||
|
||||
def clean(self):
|
||||
primary_email = self.cleaned_data.get("primary_email")
|
||||
secondary_emails = self.cleaned_data.get("secondary_emails")
|
||||
if primary_email and secondary_emails:
|
||||
if primary_email in secondary_emails:
|
||||
msg = "Primary and secondary email address must be differents"
|
||||
self._errors["primary_email"] = self.error_class([msg])
|
||||
primary_person = self.cleaned_data.get("primary_person")
|
||||
duplicate_persons = self.cleaned_data.get("duplicate_persons")
|
||||
if primary_person and duplicate_persons:
|
||||
if primary_person in duplicate_persons:
|
||||
msg = "The primary person must not also be listed as a duplicate person"
|
||||
self._errors["primary_person"] = self.error_class([msg])
|
||||
return self.cleaned_data
|
||||
|
||||
def save(self):
|
||||
primary_email = self.cleaned_data.get("primary_email")
|
||||
secondary_emails = self.cleaned_data.get("secondary_emails")
|
||||
primary_person = self.cleaned_data.get("primary_person")
|
||||
duplicate_persons = self.cleaned_data.get("duplicate_persons")
|
||||
|
||||
primary_nominee = Nominee.objects.get_by_nomcom(self.nomcom).get(email__address=primary_email)
|
||||
while primary_nominee.duplicated:
|
||||
primary_nominee = primary_nominee.duplicated
|
||||
secondary_nominees = Nominee.objects.get_by_nomcom(self.nomcom).filter(email__address__in=secondary_emails)
|
||||
for nominee in secondary_nominees:
|
||||
# move nominations
|
||||
nominee.nomination_set.all().update(nominee=primary_nominee)
|
||||
# move feedback
|
||||
for fb in nominee.feedback_set.all():
|
||||
fb.nominees.remove(nominee)
|
||||
fb.nominees.add(primary_nominee)
|
||||
# move nomineepositions
|
||||
for nominee_position in nominee.nomineeposition_set.all():
|
||||
primary_nominee_positions = NomineePosition.objects.filter(position=nominee_position.position,
|
||||
nominee=primary_nominee)
|
||||
primary_nominee_position = primary_nominee_positions and primary_nominee_positions[0] or None
|
||||
subject = "Request to merge Person records"
|
||||
from_email = settings.NOMCOM_FROM_EMAIL
|
||||
(to_email, cc) = gather_address_lists('person_merge_requested')
|
||||
context = {'primary_person':primary_person, 'duplicate_persons':duplicate_persons}
|
||||
send_mail(None, to_email, from_email, subject, 'nomcom/merge_request.txt', context, cc=cc)
|
||||
|
||||
if primary_nominee_position:
|
||||
# if already a nomineeposition object for a position and nominee,
|
||||
# update the nomineepostion of primary nominee with the state
|
||||
if nominee_position.state.slug == 'accepted' or primary_nominee_position.state.slug == 'accepted':
|
||||
primary_nominee_position.state = NomineePositionStateName.objects.get(slug='accepted')
|
||||
primary_nominee_position.save()
|
||||
if nominee_position.state.slug == 'declined' and primary_nominee_position.state.slug == 'pending':
|
||||
primary_nominee_position.state = NomineePositionStateName.objects.get(slug='declined')
|
||||
primary_nominee_position.save()
|
||||
else:
|
||||
# It is not allowed two or more nomineeposition objects with same position and nominee
|
||||
# move nominee_position object to primary nominee
|
||||
nominee_position.nominee = primary_nominee
|
||||
nominee_position.save()
|
||||
|
||||
nominee.duplicated = primary_nominee
|
||||
nominee.save()
|
||||
|
||||
secondary_nominees.update(duplicated=primary_nominee)
|
||||
|
||||
|
||||
class NominateForm(BaseNomcomForm, forms.ModelForm):
|
||||
comments = forms.CharField(label="Candidate's qualifications for the position",
|
||||
class NominateForm(forms.ModelForm):
|
||||
searched_email = SearchableEmailField(only_users=False)
|
||||
qualifications = forms.CharField(label="Candidate's qualifications for the position",
|
||||
widget=forms.Textarea())
|
||||
confirmation = forms.BooleanField(label='Email comments back to me as confirmation',
|
||||
confirmation = forms.BooleanField(label='Email comments back to me as confirmation.',
|
||||
help_text="If you want to get a confirmation mail containing your feedback in cleartext, please check the 'email comments back to me as confirmation'.",
|
||||
required=False)
|
||||
|
||||
fieldsets = [('Candidate Nomination', ('share_nominator','position', 'candidate_name',
|
||||
'candidate_email', 'candidate_phone', 'comments', 'confirmation'))]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.nomcom = kwargs.pop('nomcom', None)
|
||||
self.user = kwargs.pop('user', None)
|
||||
|
@ -339,19 +264,16 @@ class NominateForm(BaseNomcomForm, forms.ModelForm):
|
|||
|
||||
super(NominateForm, self).__init__(*args, **kwargs)
|
||||
|
||||
fieldset = ['share_nominator',
|
||||
'position',
|
||||
'candidate_name',
|
||||
'candidate_email', 'candidate_phone',
|
||||
'comments']
|
||||
|
||||
new_person_url_name = 'nomcom_%s_nominate_newperson' % ('public' if self.public else 'private' )
|
||||
self.fields['searched_email'].label = 'Candidate email'
|
||||
self.fields['searched_email'].help_text = 'Search by name or email address. Click <a href="%s">here</a> if the search does not find the candidate you want to nominate.' % reverse(new_person_url_name,kwargs={'year':self.nomcom.year()})
|
||||
self.fields['nominator_email'].label = 'Nominator email'
|
||||
if self.nomcom:
|
||||
self.fields['position'].queryset = Position.objects.get_by_nomcom(self.nomcom).opened()
|
||||
self.fields['comments'].help_text = self.nomcom.initial_text
|
||||
self.fields['qualifications'].help_text = self.nomcom.initial_text
|
||||
|
||||
if not self.public:
|
||||
fieldset = ['nominator_email'] + fieldset
|
||||
self.fields.pop('confirmation')
|
||||
author = get_user_email(self.user)
|
||||
if author:
|
||||
self.fields['nominator_email'].initial = author.address
|
||||
|
@ -363,22 +285,23 @@ class NominateForm(BaseNomcomForm, forms.ModelForm):
|
|||
has indicated they will allow NomCom to share their name as one of the people
|
||||
nominating this candidate."""
|
||||
else:
|
||||
fieldset.append('confirmation')
|
||||
self.fields.pop('nominator_email')
|
||||
|
||||
self.fieldsets = [('Candidate Nomination', fieldset)]
|
||||
|
||||
def save(self, commit=True):
|
||||
# Create nomination
|
||||
nomination = super(NominateForm, self).save(commit=False)
|
||||
nominator_email = self.cleaned_data.get('nominator_email', None)
|
||||
candidate_email = self.cleaned_data['candidate_email']
|
||||
candidate_name = self.cleaned_data['candidate_name']
|
||||
searched_email = self.cleaned_data['searched_email']
|
||||
position = self.cleaned_data['position']
|
||||
comments = self.cleaned_data['comments']
|
||||
confirmation = self.cleaned_data['confirmation']
|
||||
qualifications = self.cleaned_data['qualifications']
|
||||
confirmation = self.cleaned_data.get('confirmation', False)
|
||||
share_nominator = self.cleaned_data['share_nominator']
|
||||
nomcom_template_path = '/nomcom/%s/' % self.nomcom.group.acronym
|
||||
|
||||
nomination.candidate_name = searched_email.person.plain_name()
|
||||
nomination.candidate_email = searched_email.address
|
||||
|
||||
author = None
|
||||
if self.public:
|
||||
author = get_user_email(self.user)
|
||||
|
@ -386,11 +309,11 @@ class NominateForm(BaseNomcomForm, forms.ModelForm):
|
|||
if nominator_email:
|
||||
emails = Email.objects.filter(address=nominator_email)
|
||||
author = emails and emails[0] or None
|
||||
nominee = get_or_create_nominee(self.nomcom, candidate_name, candidate_email, position, author)
|
||||
nominee = make_nomineeposition(self.nomcom, searched_email.person, position, author)
|
||||
|
||||
# Complete nomination data
|
||||
feedback = Feedback.objects.create(nomcom=self.nomcom,
|
||||
comments=comments,
|
||||
comments=qualifications,
|
||||
type=FeedbackTypeName.objects.get(slug='nomina'),
|
||||
user=self.user)
|
||||
feedback.positions.add(position)
|
||||
|
@ -416,7 +339,117 @@ class NominateForm(BaseNomcomForm, forms.ModelForm):
|
|||
from_email = settings.NOMCOM_FROM_EMAIL
|
||||
(to_email, cc) = gather_address_lists('nomination_receipt_requested',nominator=author.address)
|
||||
context = {'nominee': nominee.email.person.name,
|
||||
'comments': comments,
|
||||
'comments': qualifications,
|
||||
'position': position.name}
|
||||
path = nomcom_template_path + NOMINATION_RECEIPT_TEMPLATE
|
||||
send_mail(None, to_email, from_email, subject, path, context, cc=cc)
|
||||
|
||||
return nomination
|
||||
|
||||
class Meta:
|
||||
model = Nomination
|
||||
fields = ('share_nominator', 'position', 'nominator_email', 'searched_email',
|
||||
'candidate_phone', 'qualifications', 'confirmation')
|
||||
|
||||
class NominateNewPersonForm(forms.ModelForm):
|
||||
qualifications = forms.CharField(label="Candidate's qualifications for the position",
|
||||
widget=forms.Textarea())
|
||||
confirmation = forms.BooleanField(label='Email comments back to me as confirmation.',
|
||||
help_text="If you want to get a confirmation mail containing your feedback in cleartext, please check the 'email comments back to me as confirmation'.",
|
||||
required=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.nomcom = kwargs.pop('nomcom', None)
|
||||
self.user = kwargs.pop('user', None)
|
||||
self.public = kwargs.pop('public', None)
|
||||
|
||||
super(NominateNewPersonForm, self).__init__(*args, **kwargs)
|
||||
|
||||
self.fields['nominator_email'].label = 'Nominator email'
|
||||
if self.nomcom:
|
||||
self.fields['position'].queryset = Position.objects.get_by_nomcom(self.nomcom).opened()
|
||||
self.fields['qualifications'].help_text = self.nomcom.initial_text
|
||||
|
||||
if not self.public:
|
||||
self.fields.pop('confirmation')
|
||||
author = get_user_email(self.user)
|
||||
if author:
|
||||
self.fields['nominator_email'].initial = author.address
|
||||
help_text = """(Nomcom Chair/Member: please fill this in. Use your own email address if the person making the
|
||||
nomination wishes to be anonymous. The confirmation email will be sent to the address given here,
|
||||
and the address will also be captured as part of the registered nomination.)"""
|
||||
self.fields['nominator_email'].help_text = help_text
|
||||
self.fields['share_nominator'].help_text = """(Nomcom Chair/Member: Check this box if the person providing this nomination
|
||||
has indicated they will allow NomCom to share their name as one of the people
|
||||
nominating this candidate."""
|
||||
else:
|
||||
self.fields.pop('nominator_email')
|
||||
|
||||
|
||||
def clean_candidate_email(self):
|
||||
candidate_email = self.cleaned_data['candidate_email']
|
||||
if Email.objects.filter(address=candidate_email).exists():
|
||||
normal_url_name = 'nomcom_%s_nominate' % 'public' if self.public else 'private'
|
||||
msg = '%s is already in the datatracker. \
|
||||
Use the <a href="%s">normal nomination form</a> to nominate the person \
|
||||
with this address.\
|
||||
' % (candidate_email,reverse(normal_url_name,kwargs={'year':self.nomcom.year()}))
|
||||
raise forms.ValidationError(mark_safe(msg))
|
||||
return candidate_email
|
||||
|
||||
def save(self, commit=True):
|
||||
# Create nomination
|
||||
nomination = super(NominateNewPersonForm, self).save(commit=False)
|
||||
nominator_email = self.cleaned_data.get('nominator_email', None)
|
||||
candidate_email = self.cleaned_data['candidate_email']
|
||||
candidate_name = self.cleaned_data['candidate_name']
|
||||
position = self.cleaned_data['position']
|
||||
qualifications = self.cleaned_data['qualifications']
|
||||
confirmation = self.cleaned_data.get('confirmation', False)
|
||||
share_nominator = self.cleaned_data['share_nominator']
|
||||
nomcom_template_path = '/nomcom/%s/' % self.nomcom.group.acronym
|
||||
|
||||
|
||||
author = None
|
||||
if self.public:
|
||||
author = get_user_email(self.user)
|
||||
else:
|
||||
if nominator_email:
|
||||
emails = Email.objects.filter(address=nominator_email)
|
||||
author = emails and emails[0] or None
|
||||
## This is where it should change - validation of the email field should fail if the email exists
|
||||
## The function should become make_nominee_from_newperson)
|
||||
nominee = make_nomineeposition_for_newperson(self.nomcom, candidate_name, candidate_email, position, author)
|
||||
|
||||
# Complete nomination data
|
||||
feedback = Feedback.objects.create(nomcom=self.nomcom,
|
||||
comments=qualifications,
|
||||
type=FeedbackTypeName.objects.get(slug='nomina'),
|
||||
user=self.user)
|
||||
feedback.positions.add(position)
|
||||
feedback.nominees.add(nominee)
|
||||
|
||||
if author:
|
||||
nomination.nominator_email = author.address
|
||||
feedback.author = author.address
|
||||
feedback.save()
|
||||
|
||||
nomination.nominee = nominee
|
||||
nomination.comments = feedback
|
||||
nomination.share_nominator = share_nominator
|
||||
nomination.user = self.user
|
||||
|
||||
if commit:
|
||||
nomination.save()
|
||||
|
||||
# send receipt email to nominator
|
||||
if confirmation:
|
||||
if author:
|
||||
subject = 'Nomination receipt'
|
||||
from_email = settings.NOMCOM_FROM_EMAIL
|
||||
(to_email, cc) = gather_address_lists('nomination_receipt_requested',nominator=author.address)
|
||||
context = {'nominee': nominee.email.person.name,
|
||||
'comments': qualifications,
|
||||
'position': position.name}
|
||||
path = nomcom_template_path + NOMINATION_RECEIPT_TEMPLATE
|
||||
send_mail(None, to_email, from_email, subject, path, context, cc=cc)
|
||||
|
@ -426,22 +459,15 @@ class NominateForm(BaseNomcomForm, forms.ModelForm):
|
|||
class Meta:
|
||||
model = Nomination
|
||||
fields = ('share_nominator', 'position', 'nominator_email', 'candidate_name',
|
||||
'candidate_email', 'candidate_phone')
|
||||
'candidate_email', 'candidate_phone', 'qualifications', 'confirmation')
|
||||
|
||||
|
||||
class FeedbackForm(BaseNomcomForm, forms.ModelForm):
|
||||
position_name = forms.CharField(label='Position',
|
||||
widget=forms.TextInput(attrs={'size': '40'}))
|
||||
nominee_name = forms.CharField(label='Nominee name',
|
||||
widget=forms.TextInput(attrs={'size': '40'}))
|
||||
nominee_email = forms.CharField(label='Nominee email',
|
||||
widget=forms.TextInput(attrs={'size': '40'}))
|
||||
nominator_email = forms.CharField(label='Commenter email')
|
||||
class FeedbackForm(forms.ModelForm):
|
||||
nominator_email = forms.CharField(label='Commenter email',required=False)
|
||||
|
||||
comments = forms.CharField(label='Comments on this nominee',
|
||||
comments = forms.CharField(label='Comments',
|
||||
widget=forms.Textarea())
|
||||
confirmation = forms.BooleanField(label='Email comments back to me as confirmation',
|
||||
help_text="If you want to get a confirmation mail containing your feedback in cleartext, please check the 'email comments back to me as confirmation'.",
|
||||
confirmation = forms.BooleanField(label='Email comments back to me as confirmation (if selected, your comments will be emailed to you in cleartext when you press Save).',
|
||||
required=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
@ -453,72 +479,44 @@ class FeedbackForm(BaseNomcomForm, forms.ModelForm):
|
|||
|
||||
super(FeedbackForm, self).__init__(*args, **kwargs)
|
||||
|
||||
readonly_fields = ['position_name',
|
||||
'nominee_name',
|
||||
'nominee_email']
|
||||
|
||||
fieldset = ['position_name',
|
||||
'nominee_name',
|
||||
'nominee_email',
|
||||
'nominator_email',
|
||||
'comments']
|
||||
author = get_user_email(self.user)
|
||||
|
||||
if self.public:
|
||||
readonly_fields += ['nominator_email']
|
||||
fieldset.append('confirmation')
|
||||
self.fields.pop('nominator_email')
|
||||
else:
|
||||
help_text = """(Nomcom Chair/Member: please fill this in. Use your own email address if the person making the
|
||||
comments wishes to be anonymous. The confirmation email will be sent to the address given here,
|
||||
and the address will also be captured as part of the registered nomination.)"""
|
||||
self.fields['nominator_email'].help_text = help_text
|
||||
self.fields['nominator_email'].required = False
|
||||
self.fields['confirmation'].label = 'Email these comments in cleartext to the provided commenter email address'
|
||||
if author:
|
||||
self.fields['nominator_email'].initial = author.address
|
||||
|
||||
author = get_user_email(self.user)
|
||||
if author:
|
||||
self.fields['nominator_email'].initial = author.address
|
||||
|
||||
if self.position and self.nominee:
|
||||
self.fields['position_name'].initial = self.position.name
|
||||
self.fields['nominee_name'].initial = self.nominee.email.person.name
|
||||
self.fields['nominee_email'].initial = self.nominee.email.address
|
||||
else:
|
||||
help_text = "Please pick a name on the nominees list"
|
||||
self.fields['position_name'].initial = help_text
|
||||
self.fields['nominee_name'].initial = help_text
|
||||
self.fields['nominee_email'].initial = help_text
|
||||
self.fields['comments'].initial = help_text
|
||||
readonly_fields += ['comments']
|
||||
self.fields['confirmation'].widget.attrs['disabled'] = "disabled"
|
||||
|
||||
for field in readonly_fields:
|
||||
self.fields[field].widget.attrs['readonly'] = True
|
||||
|
||||
self.fieldsets = [('Provide comments', fieldset)]
|
||||
|
||||
def clean(self):
|
||||
if not NomineePosition.objects.accepted().filter(nominee=self.nominee,
|
||||
position=self.position):
|
||||
msg = "There isn't a accepted nomination for %s on the %s position" % (self.nominee, self.position)
|
||||
self._errors["nominee_email"] = self.error_class([msg])
|
||||
self._errors["comments"] = self.error_class([msg])
|
||||
return self.cleaned_data
|
||||
|
||||
def save(self, commit=True):
|
||||
feedback = super(FeedbackForm, self).save(commit=False)
|
||||
confirmation = self.cleaned_data['confirmation']
|
||||
comments = self.cleaned_data['comments']
|
||||
nominator_email = self.cleaned_data['nominator_email']
|
||||
nomcom_template_path = '/nomcom/%s/' % self.nomcom.group.acronym
|
||||
|
||||
author = None
|
||||
if self.public:
|
||||
author = get_user_email(self.user)
|
||||
else:
|
||||
nominator_email = self.cleaned_data['nominator_email']
|
||||
if nominator_email:
|
||||
emails = Email.objects.filter(address=nominator_email)
|
||||
author = emails and emails[0] or None
|
||||
|
||||
if author:
|
||||
feedback.author = author
|
||||
feedback.author = author.address
|
||||
|
||||
feedback.nomcom = self.nomcom
|
||||
feedback.user = self.user
|
||||
|
@ -541,18 +539,16 @@ class FeedbackForm(BaseNomcomForm, forms.ModelForm):
|
|||
|
||||
class Meta:
|
||||
model = Feedback
|
||||
fields = ('nominee_name',
|
||||
'nominee_email',
|
||||
fields = (
|
||||
'nominator_email',
|
||||
'comments',
|
||||
'confirmation',
|
||||
'comments')
|
||||
)
|
||||
|
||||
class FeedbackEmailForm(BaseNomcomForm, forms.Form):
|
||||
class FeedbackEmailForm(forms.Form):
|
||||
|
||||
email_text = forms.CharField(label='Email text', widget=forms.Textarea())
|
||||
|
||||
fieldsets = [('Feedback email', ('email_text',))]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.nomcom = kwargs.pop('nomcom', None)
|
||||
super(FeedbackEmailForm, self).__init__(*args, **kwargs)
|
||||
|
@ -560,12 +556,10 @@ class FeedbackEmailForm(BaseNomcomForm, forms.Form):
|
|||
def save(self, commit=True):
|
||||
create_feedback_email(self.nomcom, self.cleaned_data['email_text'])
|
||||
|
||||
class QuestionnaireForm(BaseNomcomForm, forms.ModelForm):
|
||||
class QuestionnaireForm(forms.ModelForm):
|
||||
|
||||
comments = forms.CharField(label='Questionnaire response from this candidate',
|
||||
widget=forms.Textarea())
|
||||
fieldsets = [('New questionnaire response', ('nominee', 'comments'))]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.nomcom = kwargs.pop('nomcom', None)
|
||||
self.user = kwargs.pop('user', None)
|
||||
|
@ -594,21 +588,14 @@ class QuestionnaireForm(BaseNomcomForm, forms.ModelForm):
|
|||
model = Feedback
|
||||
fields = ( 'comments', )
|
||||
|
||||
class NomComTemplateForm(BaseNomcomForm, DBTemplateForm):
|
||||
class NomComTemplateForm(DBTemplateForm):
|
||||
content = forms.CharField(label="Text", widget=forms.Textarea(attrs={'cols': '120', 'rows':'40', }))
|
||||
fieldsets = [('Template content', ('content', )), ]
|
||||
|
||||
|
||||
class PositionForm(BaseNomcomForm, forms.ModelForm):
|
||||
|
||||
fieldsets = [('Position', ('name', 'description',
|
||||
'is_open', 'incumbent'))]
|
||||
|
||||
incumbent = SearchableEmailField(required=False)
|
||||
class PositionForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = Position
|
||||
fields = ('name', 'description', 'is_open', 'incumbent')
|
||||
fields = ('name', 'is_open')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.nomcom = kwargs.pop('nomcom', None)
|
||||
|
@ -619,12 +606,10 @@ class PositionForm(BaseNomcomForm, forms.ModelForm):
|
|||
super(PositionForm, self).save(*args, **kwargs)
|
||||
|
||||
|
||||
class PrivateKeyForm(BaseNomcomForm, forms.Form):
|
||||
class PrivateKeyForm(forms.Form):
|
||||
|
||||
key = forms.CharField(label='Private key', widget=forms.Textarea(), required=False)
|
||||
|
||||
fieldsets = [('Private key', ('key',))]
|
||||
|
||||
def clean_key(self):
|
||||
key = self.cleaned_data.get('key', None)
|
||||
if not key:
|
||||
|
@ -635,7 +620,7 @@ class PrivateKeyForm(BaseNomcomForm, forms.Form):
|
|||
raise forms.ValidationError('Invalid private key. Error was: %s' % error)
|
||||
|
||||
|
||||
class PendingFeedbackForm(BaseNomcomForm, forms.ModelForm):
|
||||
class PendingFeedbackForm(forms.ModelForm):
|
||||
|
||||
type = forms.ModelChoiceField(queryset=FeedbackTypeName.objects.all().order_by('pk'), widget=forms.RadioSelect, empty_label='Unclassified', required=False)
|
||||
|
||||
|
@ -643,13 +628,6 @@ class PendingFeedbackForm(BaseNomcomForm, forms.ModelForm):
|
|||
model = Feedback
|
||||
fields = ('type', )
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(PendingFeedbackForm, self).__init__(*args, **kwargs)
|
||||
try:
|
||||
self.default_type = FeedbackTypeName.objects.get(slug=settings.DEFAULT_FEEDBACK_TYPE)
|
||||
except FeedbackTypeName.DoesNotExist:
|
||||
self.default_type = None
|
||||
|
||||
def set_nomcom(self, nomcom, user):
|
||||
self.nomcom = nomcom
|
||||
self.user = user
|
||||
|
@ -665,17 +643,6 @@ class PendingFeedbackForm(BaseNomcomForm, forms.ModelForm):
|
|||
feedback.save()
|
||||
return feedback
|
||||
|
||||
def move_to_default(self):
|
||||
if not self.default_type or self.cleaned_data.get('type', None):
|
||||
return None
|
||||
feedback = super(PendingFeedbackForm, self).save(commit=False)
|
||||
feedback.nomcom = self.nomcom
|
||||
feedback.user = self.user
|
||||
feedback.type = self.default_type
|
||||
feedback.save()
|
||||
return feedback
|
||||
|
||||
|
||||
class ReminderDatesForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
|
@ -711,17 +678,38 @@ class MutableFeedbackForm(forms.ModelForm):
|
|||
if self.feedback_type.slug != 'nomina':
|
||||
self.fields['nominee'] = MultiplePositionNomineeField(nomcom=self.nomcom,
|
||||
required=True,
|
||||
widget=forms.SelectMultiple,
|
||||
widget=forms.SelectMultiple(attrs={'class':'nominee_multi_select','size':'12'}),
|
||||
help_text='Hold down "Control", or "Command" on a Mac, to select more than one.')
|
||||
else:
|
||||
self.fields['position'] = forms.ModelChoiceField(queryset=Position.objects.get_by_nomcom(self.nomcom).opened(), label="Position")
|
||||
self.fields['candidate_name'] = forms.CharField(label="Candidate name")
|
||||
self.fields['candidate_email'] = forms.EmailField(label="Candidate email")
|
||||
self.fields['searched_email'] = SearchableEmailField(only_users=False,help_text="Try to find the candidate you are classifying with this field first. Only use the name and email fields below if this search does not find the candidate.",label="Candidate",required=False)
|
||||
self.fields['candidate_name'] = forms.CharField(label="Candidate name",help_text="Only fill in this name field if the search doesn't find the person you are classifying",required=False)
|
||||
self.fields['candidate_email'] = forms.EmailField(label="Candidate email",help_text="Only fill in this email field if the search doesn't find the person you are classifying",required=False)
|
||||
self.fields['candidate_phone'] = forms.CharField(label="Candidate phone", required=False)
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(MutableFeedbackForm,self).clean()
|
||||
if self.feedback_type.slug == 'nomina':
|
||||
searched_email = self.cleaned_data.get('searched_email')
|
||||
candidate_name = self.cleaned_data.get('candidate_name')
|
||||
if candidate_name:
|
||||
candidate_name = candidate_name.strip()
|
||||
candidate_email = self.cleaned_data.get('candidate_email')
|
||||
if candidate_email:
|
||||
candidate_email = candidate_email.strip()
|
||||
|
||||
if not any([ searched_email and not candidate_name and not candidate_email,
|
||||
not searched_email and candidate_name and candidate_email,
|
||||
]):
|
||||
raise forms.ValidationError("You must identify either an existing person (by searching with the candidate field) and leave the name and email fields blank, or leave the search field blank and provide both a name and email address.")
|
||||
if candidate_email and Email.objects.filter(address=candidate_email).exists():
|
||||
raise forms.ValidationError("%s already exists in the datatracker. Please search within the candidate field to find it and leave both the name and email fields blank." % candidate_email)
|
||||
return cleaned_data
|
||||
|
||||
def save(self, commit=True):
|
||||
feedback = super(MutableFeedbackForm, self).save(commit=False)
|
||||
if self.instance.type.slug == 'nomina':
|
||||
searched_email = self.cleaned_data['searched_email']
|
||||
candidate_email = self.cleaned_data['candidate_email']
|
||||
candidate_name = self.cleaned_data['candidate_name']
|
||||
candidate_phone = self.cleaned_data['candidate_phone']
|
||||
|
@ -733,7 +721,10 @@ class MutableFeedbackForm(forms.ModelForm):
|
|||
emails = Email.objects.filter(address=nominator_email)
|
||||
author = emails and emails[0] or None
|
||||
|
||||
nominee = get_or_create_nominee(self.nomcom, candidate_name, candidate_email, position, author)
|
||||
if searched_email:
|
||||
nominee = make_nomineeposition(self.nomcom, searched_email.person, position, author)
|
||||
else:
|
||||
nominee = make_nomineeposition_for_newperson(self.nomcom, candidate_name, candidate_email, position, author)
|
||||
feedback.nominees.add(nominee)
|
||||
feedback.positions.add(position)
|
||||
Nomination.objects.create(
|
||||
|
@ -768,41 +759,25 @@ FullFeedbackFormSet = forms.modelformset_factory(
|
|||
|
||||
class EditNomineeForm(forms.ModelForm):
|
||||
|
||||
nominee_email = forms.EmailField(label="Nominee email",
|
||||
widget=forms.TextInput(attrs={'size': '40'}))
|
||||
nominee_email = forms.ModelChoiceField(queryset=Email.objects.none(),empty_label=None)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(EditNomineeForm, self).__init__(*args, **kwargs)
|
||||
self.fields['nominee_email'].initial = self.instance.email.address
|
||||
self.fields['nominee_email'].queryset = Email.objects.filter(person=self.instance.person,active=True)
|
||||
self.fields['nominee_email'].initial = self.instance.email
|
||||
self.fields['nominee_email'].help_text = "If the address you are looking for does not appear in this list, ask the nominee (or the secretariat) to add the address to thier datatracker account and ensure it is marked as active."
|
||||
|
||||
def save(self, commit=True):
|
||||
nominee = super(EditNomineeForm, self).save(commit=False)
|
||||
nominee_email = self.cleaned_data.get("nominee_email")
|
||||
if nominee_email != nominee.email.address:
|
||||
# create a new nominee with the new email
|
||||
new_email, created_email = Email.objects.get_or_create(address=nominee_email)
|
||||
new_email.person = nominee.email.person
|
||||
new_email.save()
|
||||
|
||||
# Chage emails between nominees
|
||||
old_email = nominee.email
|
||||
nominee.email = new_email
|
||||
nominee.save()
|
||||
new_nominee = Nominee.objects.create(email=old_email, nomcom=nominee.nomcom)
|
||||
|
||||
# new nominees point to old nominee
|
||||
new_nominee.duplicated = nominee
|
||||
new_nominee.save()
|
||||
|
||||
nominee.email = nominee_email
|
||||
nominee.save()
|
||||
return nominee
|
||||
|
||||
class Meta:
|
||||
model = Nominee
|
||||
fields = ('nominee_email',)
|
||||
|
||||
def clean_nominee_email(self):
|
||||
nominee_email = self.cleaned_data['nominee_email']
|
||||
nominees = Nominee.objects.exclude(email__address=self.instance.email.address).filter(email__address=nominee_email)
|
||||
if nominees:
|
||||
raise forms.ValidationError('This emails already does exists in another nominee, please go to merge form')
|
||||
return nominee_email
|
||||
class NominationResponseCommentForm(forms.Form):
|
||||
comments = forms.CharField(widget=forms.Textarea,required=False,help_text="Any comments provided will be encrytped and will only be visible to the NomCom.")
|
||||
|
||||
|
|
18
ietf/nomcom/migrations/0005_remove_position_incumbent.py
Normal file
18
ietf/nomcom/migrations/0005_remove_position_incumbent.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('nomcom', '0004_auto_20151027_0829'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='position',
|
||||
name='incumbent',
|
||||
),
|
||||
]
|
|
@ -0,0 +1,67 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def set_new_template_content(apps, schema_editor):
|
||||
DBTemplate = apps.get_model('dbtemplate','DBTemplate')
|
||||
|
||||
h = DBTemplate.objects.get(path='/nomcom/defaults/position/header_questionnaire.txt')
|
||||
h.content = """Hi $nominee, this is the questionnaire for the position $position.
|
||||
Please follow the directions in the questionnaire closely - you may see
|
||||
that some changes have been made from previous years, so please take note.
|
||||
|
||||
We look forward to reading your questionnaire response! If you have any
|
||||
administrative questions, please send mail to nomcom-chair@ietf.org.
|
||||
|
||||
You may have received this questionnaire before accepting the nomination. A
|
||||
separate message, sent at the time of nomination, provides instructions for
|
||||
indicating whether you accept or decline. If you have not completed those
|
||||
steps, please do so as soon as possible, or contact the nomcom chair.
|
||||
|
||||
Thank you!
|
||||
|
||||
|
||||
"""
|
||||
h.save()
|
||||
|
||||
h = DBTemplate.objects.get(path='/nomcom/defaults/position/questionnaire.txt')
|
||||
h.content = """NomCom Chair: Replace this content with the appropriate questionnaire for the position $position.
|
||||
"""
|
||||
h.save()
|
||||
|
||||
def revert_to_old_template_content(apps, schema_editor):
|
||||
DBTemplate = apps.get_model('dbtemplate','DBTemplate')
|
||||
|
||||
h = DBTemplate.objects.get(path='/nomcom/defaults/position/header_questionnaire.txt')
|
||||
h.content = """Hi $nominee, this is the questionnaire for the position $position.
|
||||
Please follow the directions in the questionnaire closely - you may see
|
||||
that some changes have been made from previous years, so please take note.
|
||||
|
||||
We look forward to reading your questionnaire response! If you have any
|
||||
administrative questions, please send mail to nomcom-chair@ietf.org.
|
||||
|
||||
Thank you!
|
||||
|
||||
|
||||
"""
|
||||
h.save()
|
||||
|
||||
h = DBTemplate.objects.get(path='/nomcom/defaults/position/questionnaire.txt')
|
||||
h.content = """Enter here the questionnaire for the position $position:
|
||||
|
||||
Questionnaire
|
||||
|
||||
"""
|
||||
h.save()
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('nomcom', '0005_remove_position_incumbent'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(set_new_template_content,revert_to_old_template_content)
|
||||
]
|
48
ietf/nomcom/migrations/0007_feedbacklastseen.py
Normal file
48
ietf/nomcom/migrations/0007_feedbacklastseen.py
Normal file
|
@ -0,0 +1,48 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import datetime
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
def create_lastseen(apps, schema_editor):
|
||||
NomCom = apps.get_model('nomcom','NomCom')
|
||||
FeedbackLastSeen = apps.get_model('nomcom','FeedbackLastSeen')
|
||||
now = datetime.datetime.now()
|
||||
for nc in NomCom.objects.all():
|
||||
reviewers = [r.person for r in nc.group.role_set.all()]
|
||||
nominees = nc.nominee_set.all()
|
||||
for r in reviewers:
|
||||
for n in nominees:
|
||||
FeedbackLastSeen.objects.create(reviewer=r,nominee=n,time=now)
|
||||
|
||||
def remove_lastseen(apps, schema_editor):
|
||||
FeedbackLastSeen = apps.get_model('nomcom','FeedbackLastSeen')
|
||||
FeedbackLastSeen.objects.delete()
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('person', '0004_auto_20150308_0440'),
|
||||
('group', '0006_auto_20150718_0509'),
|
||||
('nomcom', '0006_improve_default_questionnaire_templates'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
|
||||
migrations.CreateModel(
|
||||
name='FeedbackLastSeen',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('time', models.DateTimeField(auto_now=True)),
|
||||
('nominee', models.ForeignKey(to='nomcom.Nominee')),
|
||||
('reviewer', models.ForeignKey(to='person.Person')),
|
||||
],
|
||||
options={
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
|
||||
migrations.RunPython(create_lastseen,remove_lastseen)
|
||||
|
||||
]
|
24
ietf/nomcom/migrations/0008_auto_20151209_1423.py
Normal file
24
ietf/nomcom/migrations/0008_auto_20151209_1423.py
Normal 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,
|
||||
),
|
||||
]
|
|
@ -0,0 +1,50 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def remove_extension(apps, schema_editor):
|
||||
DBTemplate = apps.get_model('dbtemplate','DBTemplate')
|
||||
for template in DBTemplate.objects.filter(path__endswith="requirements.txt"):
|
||||
template.path = template.path[:-4]
|
||||
template.save()
|
||||
|
||||
def restore_extension(apps, schema_editor):
|
||||
DBTemplate = apps.get_model('dbtemplate','DBTemplate')
|
||||
for template in DBTemplate.objects.filter(path__endswith="requirements"):
|
||||
template.path = template.path+".txt"
|
||||
template.save()
|
||||
|
||||
def default_rst(apps, schema_editor):
|
||||
DBTemplate = apps.get_model('dbtemplate','DBTemplate')
|
||||
default_req = DBTemplate.objects.get(path__startswith='/nomcom/defaults/position/requirements')
|
||||
default_req.type_id = 'rst'
|
||||
default_req.save()
|
||||
|
||||
def default_plain(apps, schema_editor):
|
||||
DBTemplate = apps.get_model('dbtemplate','DBTemplate')
|
||||
default_req = DBTemplate.objects.get(path__startswith='/nomcom/defaults/position/requirements')
|
||||
default_req.type_id = 'plain'
|
||||
default_req.save()
|
||||
|
||||
def rst_2015(apps, schema_editor):
|
||||
DBTemplate = apps.get_model('dbtemplate','DBTemplate')
|
||||
DBTemplate.objects.filter(path__startswith='/nomcom/nomcom2015/').filter(path__contains='position/requirements').exclude(path__contains='/27/').update(type_id='rst')
|
||||
|
||||
def plain_2015(apps, schema_editor):
|
||||
DBTemplate = apps.get_model('dbtemplate','DBTemplate')
|
||||
DBTemplate.objects.filter(path__startswith='/nomcom/nomcom2015/').filter(path__contains='position/requirements').update(type_id='plain')
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('nomcom', '0008_auto_20151209_1423'),
|
||||
('dbtemplate', '0002_auto_20141222_1749'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(remove_extension,restore_extension),
|
||||
migrations.RunPython(default_rst,default_plain),
|
||||
migrations.RunPython(rst_2015,plain_2015),
|
||||
]
|
27
ietf/nomcom/migrations/0010_nominee_person.py
Normal file
27
ietf/nomcom/migrations/0010_nominee_person.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
def populate_person(apps, schema_editor):
|
||||
Nominee = apps.get_model('nomcom','Nominee')
|
||||
for n in Nominee.objects.all():
|
||||
n.person = n.email.person
|
||||
n.save()
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('person', '0004_auto_20150308_0440'),
|
||||
('nomcom', '0009_remove_requirements_dbtemplate_type_from_path'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='nominee',
|
||||
name='person',
|
||||
field=models.ForeignKey(blank=True, to='person.Person', null=True),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.RunPython(populate_person,None)
|
||||
]
|
|
@ -7,9 +7,10 @@ from django.conf import settings
|
|||
from django.core.files.storage import FileSystemStorage
|
||||
from django.contrib.auth.models import User
|
||||
from django.template.loader import render_to_string
|
||||
from django.template.defaultfilters import linebreaks
|
||||
|
||||
from ietf.nomcom.fields import EncryptedTextField
|
||||
from ietf.person.models import Email
|
||||
from ietf.person.models import Person,Email
|
||||
from ietf.group.models import Group
|
||||
from ietf.name.models import NomineePositionStateName, FeedbackTypeName
|
||||
from ietf.dbtemplate.models import DBTemplate
|
||||
|
@ -66,6 +67,14 @@ class NomCom(models.Model):
|
|||
if created:
|
||||
initialize_templates_for_group(self)
|
||||
|
||||
def year(self):
|
||||
year = getattr(self,'_cached_year',None)
|
||||
if year is None:
|
||||
if self.group and self.group.acronym.startswith('nomcom'):
|
||||
year = int(self.group.acronym[6:])
|
||||
self._cached_year = year
|
||||
return year
|
||||
|
||||
|
||||
def delete_nomcom(sender, **kwargs):
|
||||
nomcom = kwargs.get('instance', None)
|
||||
|
@ -102,6 +111,7 @@ class Nomination(models.Model):
|
|||
class Nominee(models.Model):
|
||||
|
||||
email = models.ForeignKey(Email)
|
||||
person = models.ForeignKey(Person, blank=True, null=True)
|
||||
nominee_position = models.ManyToManyField('Position', through='NomineePosition')
|
||||
duplicated = models.ForeignKey('Nominee', blank=True, null=True)
|
||||
nomcom = models.ForeignKey('NomCom')
|
||||
|
@ -118,6 +128,12 @@ class Nominee(models.Model):
|
|||
else:
|
||||
return self.email.address
|
||||
|
||||
def name(self):
|
||||
if self.email.person and self.email.person.name:
|
||||
return u'%s' % (self.email.person.plain_name(),)
|
||||
else:
|
||||
return self.email.address
|
||||
|
||||
|
||||
class NomineePosition(models.Model):
|
||||
|
||||
|
@ -150,12 +166,10 @@ 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)
|
||||
incumbent = models.ForeignKey(Email, null=True, blank=True)
|
||||
|
||||
objects = PositionManager()
|
||||
|
||||
|
@ -189,7 +203,10 @@ class Position(models.Model):
|
|||
return render_to_string(self.questionnaire.path, {'position': self})
|
||||
|
||||
def get_requirement(self):
|
||||
return render_to_string(self.requirement.path, {'position': self})
|
||||
rendered = render_to_string(self.requirement.path, {'position': self})
|
||||
if self.requirement.type_id=='plain':
|
||||
rendered = linebreaks(rendered)
|
||||
return rendered
|
||||
|
||||
|
||||
class Feedback(models.Model):
|
||||
|
@ -211,4 +228,7 @@ class Feedback(models.Model):
|
|||
class Meta:
|
||||
ordering = ['time']
|
||||
|
||||
|
||||
class FeedbackLastSeen(models.Model):
|
||||
reviewer = models.ForeignKey(Person)
|
||||
nominee = models.ForeignKey(Nominee)
|
||||
time = models.DateTimeField(auto_now=True)
|
||||
|
|
|
@ -25,13 +25,11 @@ class NomComResource(ModelResource):
|
|||
}
|
||||
api.nomcom.register(NomComResource())
|
||||
|
||||
from ietf.person.resources import EmailResource
|
||||
from ietf.dbtemplate.resources import DBTemplateResource
|
||||
class PositionResource(ModelResource):
|
||||
nomcom = ToOneField(NomComResource, 'nomcom')
|
||||
requirement = ToOneField(DBTemplateResource, 'requirement', null=True)
|
||||
questionnaire = ToOneField(DBTemplateResource, 'questionnaire', null=True)
|
||||
incumbent = ToOneField(EmailResource, 'incumbent', null=True)
|
||||
class Meta:
|
||||
queryset = Position.objects.all()
|
||||
serializer = api.Serializer()
|
||||
|
@ -39,12 +37,10 @@ class PositionResource(ModelResource):
|
|||
filtering = {
|
||||
"id": ALL,
|
||||
"name": ALL,
|
||||
"description": ALL,
|
||||
"is_open": ALL,
|
||||
"nomcom": ALL_WITH_RELATIONS,
|
||||
"requirement": ALL_WITH_RELATIONS,
|
||||
"questionnaire": ALL_WITH_RELATIONS,
|
||||
"incumbent": ALL_WITH_RELATIONS,
|
||||
}
|
||||
api.nomcom.register(PositionResource())
|
||||
|
||||
|
@ -148,3 +144,17 @@ class NominationResource(ModelResource):
|
|||
}
|
||||
api.nomcom.register(NominationResource())
|
||||
|
||||
from ietf.person.resources import PersonResource
|
||||
class FeedbackLastSeenResource(ModelResource):
|
||||
reviewer = ToOneField(PersonResource, 'reviewer')
|
||||
nominee = ToOneField(NomineeResource, 'nominee')
|
||||
class Meta:
|
||||
queryset = FeedbackLastSeen.objects.all()
|
||||
serializer = api.Serializer()
|
||||
filtering = {
|
||||
"id": ALL,
|
||||
"time": ALL,
|
||||
"reviewer": ALL_WITH_RELATIONS,
|
||||
"nominee": ALL_WITH_RELATIONS,
|
||||
}
|
||||
api.nomcom.register(FeedbackLastSeenResource())
|
||||
|
|
|
@ -7,43 +7,32 @@ from django.template.defaultfilters import linebreaksbr, force_escape
|
|||
|
||||
from ietf.utils.pipe import pipe
|
||||
from ietf.utils.log import log
|
||||
from ietf.ietfauth.utils import has_role
|
||||
from ietf.doc.templatetags.ietf_filters import wrap_text
|
||||
|
||||
from ietf.person.models import Person
|
||||
from ietf.nomcom.models import Feedback
|
||||
from ietf.nomcom.utils import get_nomcom_by_year, get_user_email, retrieve_nomcom_private_key
|
||||
from ietf.nomcom.utils import get_nomcom_by_year, retrieve_nomcom_private_key
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.filter
|
||||
def is_chair(user, year):
|
||||
def is_chair_or_advisor(user, year):
|
||||
if not user or not year:
|
||||
return False
|
||||
nomcom = get_nomcom_by_year(year=year)
|
||||
if has_role(user, "Secretariat"):
|
||||
return True
|
||||
return nomcom.group.has_role(user, "chair")
|
||||
return nomcom.group.has_role(user, ["chair","advisor"])
|
||||
|
||||
|
||||
@register.filter
|
||||
def has_publickey(nomcom):
|
||||
return nomcom and nomcom.public_key and True or False
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def add_num_nominations(user, position, nominee):
|
||||
author = get_user_email(user)
|
||||
|
||||
count = Feedback.objects.filter(positions__in=[position],
|
||||
nominees__in=[nominee],
|
||||
author=author,
|
||||
type='comment').count()
|
||||
|
||||
return '<span class="badge" title="%d earlier comments from you on %s as %s">%s</span> ' % (count, nominee.email.address, position, count)
|
||||
|
||||
@register.filter
|
||||
def lookup(container,key):
|
||||
return container and container.get(key,None)
|
||||
|
||||
@register.filter
|
||||
def formatted_email(address):
|
||||
|
|
|
@ -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,12 +127,10 @@ 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,
|
||||
incumbent=email)
|
||||
is_open=True)
|
||||
|
||||
ChangeStateGroupEvent.objects.get_or_create(group=group,
|
||||
type="changed_state",
|
||||
|
|
1148
ietf/nomcom/tests.py
1148
ietf/nomcom/tests.py
File diff suppressed because it is too large
Load diff
|
@ -1,5 +1,4 @@
|
|||
from django.conf.urls import patterns, url
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
from ietf.nomcom.forms import EditMembersForm, EditMembersFormPreview
|
||||
|
||||
|
@ -8,7 +7,9 @@ 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/nominate/newperson$', 'private_nominate_newperson', name='nomcom_private_nominate_newperson'),
|
||||
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'),
|
||||
url(r'^(?P<year>\d{4})/private/questionnaire-response/$', 'private_questionnaire', name='nomcom_private_questionnaire'),
|
||||
|
@ -22,8 +23,6 @@ urlpatterns = patterns('ietf.nomcom.views',
|
|||
url(r'^(?P<year>\d{4})/private/send-reminder-mail/(?P<type>\w+)/$', 'send_reminder_mail', name='nomcom_send_reminder_mail'),
|
||||
url(r'^(?P<year>\d{4})/private/edit-members/$', EditMembersFormPreview(EditMembersForm), name='nomcom_edit_members'),
|
||||
url(r'^(?P<year>\d{4})/private/edit-nomcom/$', 'edit_nomcom', name='nomcom_edit_nomcom'),
|
||||
url(r'^(?P<year>\d{4})/private/delete-nomcom/$', 'delete_nomcom', name='nomcom_delete_nomcom'),
|
||||
url(r'^deleted/$', TemplateView.as_view(template_name='nomcom/deleted.html'), name='nomcom_deleted'),
|
||||
url(r'^(?P<year>\d{4})/private/chair/templates/$', 'list_templates', name='nomcom_list_templates'),
|
||||
url(r'^(?P<year>\d{4})/private/chair/templates/(?P<template_id>\d+)/$', 'edit_template', name='nomcom_edit_template'),
|
||||
url(r'^(?P<year>\d{4})/private/chair/position/$', 'list_positions', name='nomcom_list_positions'),
|
||||
|
@ -37,6 +36,7 @@ urlpatterns = patterns('ietf.nomcom.views',
|
|||
url(r'^(?P<year>\d{4})/questionnaires/$', 'questionnaires', name='nomcom_questionnaires'),
|
||||
url(r'^(?P<year>\d{4})/feedback/$', 'public_feedback', name='nomcom_public_feedback'),
|
||||
url(r'^(?P<year>\d{4})/nominate/$', 'public_nominate', name='nomcom_public_nominate'),
|
||||
url(r'^(?P<year>\d{4})/nominate/newperson$', 'public_nominate_newperson', name='nomcom_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]+)/$', 'process_nomination_status', name='nomcom_process_nomination_status'),
|
||||
)
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ import debug # pyflakes:ignore
|
|||
MAIN_NOMCOM_TEMPLATE_PATH = '/nomcom/defaults/'
|
||||
QUESTIONNAIRE_TEMPLATE = 'position/questionnaire.txt'
|
||||
HEADER_QUESTIONNAIRE_TEMPLATE = 'position/header_questionnaire.txt'
|
||||
REQUIREMENTS_TEMPLATE = 'position/requirements.txt'
|
||||
REQUIREMENTS_TEMPLATE = 'position/requirements'
|
||||
HOME_TEMPLATE = 'home.rst'
|
||||
INEXISTENT_PERSON_TEMPLATE = 'email/inexistent_person.txt'
|
||||
NOMINEE_EMAIL_TEMPLATE = 'email/new_nominee.txt'
|
||||
|
@ -53,7 +53,7 @@ def get_nomcom_by_year(year):
|
|||
from ietf.nomcom.models import NomCom
|
||||
return get_object_or_404(NomCom,
|
||||
group__acronym__icontains=year,
|
||||
group__state__slug='active')
|
||||
)
|
||||
|
||||
|
||||
def get_year_by_nomcom(nomcom):
|
||||
|
@ -271,42 +271,22 @@ def send_reminder_to_nominees(nominees,type):
|
|||
return addrs
|
||||
|
||||
|
||||
def get_or_create_nominee(nomcom, candidate_name, candidate_email, position, author):
|
||||
def make_nomineeposition(nomcom, candidate, position, author):
|
||||
from ietf.nomcom.models import Nominee, NomineePosition
|
||||
|
||||
nomcom_template_path = '/nomcom/%s/' % nomcom.group.acronym
|
||||
|
||||
# Create person and email if candidate email does't exist and send email
|
||||
email, created_email = Email.objects.get_or_create(address=candidate_email)
|
||||
if created_email:
|
||||
person = Person.objects.create(name=candidate_name,
|
||||
ascii=unaccent.asciify(candidate_name),
|
||||
address=candidate_email)
|
||||
email.person = person
|
||||
email.save()
|
||||
|
||||
# Add the nomination for a particular position
|
||||
nominee, created = Nominee.objects.get_or_create(email=email, nomcom=nomcom)
|
||||
nominee, created = Nominee.objects.get_or_create(person=candidate,email=candidate.email(), nomcom=nomcom)
|
||||
while nominee.duplicated:
|
||||
nominee = nominee.duplicated
|
||||
nominee_position, nominee_position_created = NomineePosition.objects.get_or_create(position=position, nominee=nominee)
|
||||
|
||||
if created_email:
|
||||
# send email to secretariat and nomcomchair to warn about the new person
|
||||
subject = 'New person is created'
|
||||
from_email = settings.NOMCOM_FROM_EMAIL
|
||||
(to_email, cc) = gather_address_lists('nomination_created_person',nomcom=nomcom)
|
||||
context = {'email': email.address,
|
||||
'fullname': email.person.name,
|
||||
'person_id': email.person.id}
|
||||
path = nomcom_template_path + INEXISTENT_PERSON_TEMPLATE
|
||||
send_mail(None, to_email, from_email, subject, path, context, cc=cc)
|
||||
|
||||
if nominee_position_created:
|
||||
# send email to nominee
|
||||
subject = 'IETF Nomination Information'
|
||||
from_email = settings.NOMCOM_FROM_EMAIL
|
||||
(to_email, cc) = gather_address_lists('nomination_new_nominee',nominee=email.address)
|
||||
(to_email, cc) = gather_address_lists('nomination_new_nominee',nominee=nominee.email.address)
|
||||
domain = Site.objects.get_current().domain
|
||||
today = datetime.date.today().strftime('%Y%m%d')
|
||||
hash = get_hash_nominee_position(today, nominee_position.id)
|
||||
|
@ -325,7 +305,7 @@ def get_or_create_nominee(nomcom, candidate_name, candidate_email, position, aut
|
|||
today,
|
||||
hash))
|
||||
|
||||
context = {'nominee': email.person.name,
|
||||
context = {'nominee': nominee.person.name,
|
||||
'position': position.name,
|
||||
'domain': domain,
|
||||
'accept_url': accept_url,
|
||||
|
@ -338,8 +318,8 @@ def get_or_create_nominee(nomcom, candidate_name, candidate_email, position, aut
|
|||
if nomcom.send_questionnaire:
|
||||
subject = '%s Questionnaire' % position
|
||||
from_email = settings.NOMCOM_FROM_EMAIL
|
||||
(to_email, cc) = gather_address_lists('nomcom_questionnaire',nominee=email.address)
|
||||
context = {'nominee': email.person.name,
|
||||
(to_email, cc) = gather_address_lists('nomcom_questionnaire',nominee=nominee.email.address)
|
||||
context = {'nominee': nominee.person.name,
|
||||
'position': position.name}
|
||||
path = '%s%d/%s' % (nomcom_template_path,
|
||||
position.id, HEADER_QUESTIONNAIRE_TEMPLATE)
|
||||
|
@ -353,8 +333,8 @@ def get_or_create_nominee(nomcom, candidate_name, candidate_email, position, aut
|
|||
subject = 'Nomination Information'
|
||||
from_email = settings.NOMCOM_FROM_EMAIL
|
||||
(to_email, cc) = gather_address_lists('nomination_received',nomcom=nomcom)
|
||||
context = {'nominee': email.person.name,
|
||||
'nominee_email': email.address,
|
||||
context = {'nominee': nominee.person.name,
|
||||
'nominee_email': nominee.email.address,
|
||||
'position': position.name}
|
||||
|
||||
if author:
|
||||
|
@ -369,6 +349,28 @@ def get_or_create_nominee(nomcom, candidate_name, candidate_email, position, aut
|
|||
|
||||
return nominee
|
||||
|
||||
def make_nomineeposition_for_newperson(nomcom, candidate_name, candidate_email, position, author):
|
||||
|
||||
# This is expected to fail if called with an existing email address
|
||||
email = Email.objects.create(address=candidate_email)
|
||||
person = Person.objects.create(name=candidate_name,
|
||||
ascii=unaccent.asciify(candidate_name),
|
||||
address=candidate_email)
|
||||
email.person = person
|
||||
email.save()
|
||||
|
||||
# send email to secretariat and nomcomchair to warn about the new person
|
||||
subject = 'New person is created'
|
||||
from_email = settings.NOMCOM_FROM_EMAIL
|
||||
(to_email, cc) = gather_address_lists('nomination_created_person',nomcom=nomcom)
|
||||
context = {'email': email.address,
|
||||
'fullname': email.person.name,
|
||||
'person_id': email.person.id}
|
||||
nomcom_template_path = '/nomcom/%s/' % nomcom.group.acronym
|
||||
path = nomcom_template_path + INEXISTENT_PERSON_TEMPLATE
|
||||
send_mail(None, to_email, from_email, subject, path, context, cc=cc)
|
||||
|
||||
return make_nomineeposition(nomcom, email.person, position, author)
|
||||
|
||||
def getheader(header_text, default="ascii"):
|
||||
"""Decode the specified header"""
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import datetime
|
||||
import re
|
||||
from collections import OrderedDict
|
||||
from collections import OrderedDict, Counter
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.contrib import messages
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.http import Http404, HttpResponseRedirect, HttpResponseForbidden
|
||||
|
@ -15,31 +16,29 @@ from django.forms.models import modelformset_factory, inlineformset_factory
|
|||
|
||||
|
||||
from ietf.dbtemplate.models import DBTemplate
|
||||
from ietf.dbtemplate.views import template_edit
|
||||
from ietf.dbtemplate.views import template_edit, template_show
|
||||
from ietf.name.models import NomineePositionStateName, FeedbackTypeName
|
||||
from ietf.group.models import Group, GroupEvent
|
||||
from ietf.message.models import Message
|
||||
|
||||
from ietf.nomcom.decorators import nomcom_private_key_required
|
||||
from ietf.nomcom.forms import (NominateForm, FeedbackForm, QuestionnaireForm,
|
||||
from ietf.nomcom.forms import (NominateForm, NominateNewPersonForm, FeedbackForm, QuestionnaireForm,
|
||||
MergeForm, NomComTemplateForm, PositionForm,
|
||||
PrivateKeyForm, EditNomcomForm, EditNomineeForm,
|
||||
PendingFeedbackForm, ReminderDatesForm, FullFeedbackFormSet,
|
||||
FeedbackEmailForm)
|
||||
from ietf.nomcom.models import Position, NomineePosition, Nominee, Feedback, NomCom, ReminderDates
|
||||
FeedbackEmailForm, NominationResponseCommentForm)
|
||||
from ietf.nomcom.models import Position, NomineePosition, Nominee, Feedback, NomCom, ReminderDates, FeedbackLastSeen
|
||||
from ietf.nomcom.utils import (get_nomcom_by_year, store_nomcom_private_key,
|
||||
get_hash_nominee_position, send_reminder_to_nominees,
|
||||
HOME_TEMPLATE, NOMINEE_ACCEPT_REMINDER_TEMPLATE,NOMINEE_QUESTIONNAIRE_REMINDER_TEMPLATE)
|
||||
from ietf.ietfauth.utils import role_required
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
def index(request):
|
||||
nomcom_list = Group.objects.filter(type__slug='nomcom').order_by('acronym')
|
||||
for nomcom in nomcom_list:
|
||||
year = nomcom.acronym[6:]
|
||||
try:
|
||||
year = int(year)
|
||||
except ValueError:
|
||||
year = None
|
||||
year = int(nomcom.acronym[6:])
|
||||
nomcom.year = year
|
||||
nomcom.label = "%s/%s" % (year, year+1)
|
||||
if year in [ 2005, 2006, 2007, 2008, 2009, 2010 ]:
|
||||
|
@ -107,11 +106,11 @@ def announcements(request):
|
|||
@role_required("Nomcom")
|
||||
def private_key(request, year):
|
||||
nomcom = get_nomcom_by_year(year)
|
||||
message = None
|
||||
|
||||
if request.session.get('NOMCOM_PRIVATE_KEY_%s' % year, None):
|
||||
message = ('warning', 'You already have a private decryption key set for this session.')
|
||||
messages.warning(request, 'You already have a private decryption key set for this session.')
|
||||
else:
|
||||
message = ('warning', "You don't have a private decryption key set for this session yet")
|
||||
messages.warning(request, "You don't have a private decryption key set for this session yet")
|
||||
|
||||
back_url = request.GET.get('back_to', reverse('nomcom_private_index', None, args=(year, )))
|
||||
if request.method == 'POST':
|
||||
|
@ -126,7 +125,6 @@ def private_key(request, year):
|
|||
'year': year,
|
||||
'back_url': back_url,
|
||||
'form': form,
|
||||
'message': message,
|
||||
'selected': 'private_key'}, RequestContext(request))
|
||||
|
||||
|
||||
|
@ -135,23 +133,25 @@ def private_index(request, year):
|
|||
nomcom = get_nomcom_by_year(year)
|
||||
all_nominee_positions = NomineePosition.objects.get_by_nomcom(nomcom).not_duplicated()
|
||||
is_chair = nomcom.group.has_role(request.user, "chair")
|
||||
message = None
|
||||
if is_chair and request.method == 'POST':
|
||||
action = request.POST.get('action')
|
||||
nominations_to_modify = request.POST.getlist('selected')
|
||||
if nominations_to_modify:
|
||||
nominations = all_nominee_positions.filter(id__in=nominations_to_modify)
|
||||
if action == "set_as_accepted":
|
||||
nominations.update(state='accepted')
|
||||
message = ('success', 'The selected nominations have been set as accepted')
|
||||
elif action == "set_as_declined":
|
||||
nominations.update(state='declined')
|
||||
message = ('success', 'The selected nominations have been set as declined')
|
||||
elif action == "set_as_pending":
|
||||
nominations.update(state='pending')
|
||||
message = ('success', 'The selected nominations have been set as pending')
|
||||
if nomcom.group.state_id != 'active':
|
||||
messages.warning(request, "This nomcom is not active. Request administrative assistance if Nominee state needs to change.")
|
||||
else:
|
||||
message = ('warning', "Please, select some nominations to work with")
|
||||
action = request.POST.get('action')
|
||||
nominations_to_modify = request.POST.getlist('selected')
|
||||
if nominations_to_modify:
|
||||
nominations = all_nominee_positions.filter(id__in=nominations_to_modify)
|
||||
if action == "set_as_accepted":
|
||||
nominations.update(state='accepted')
|
||||
messages.success(request,'The selected nominations have been set as accepted')
|
||||
elif action == "set_as_declined":
|
||||
nominations.update(state='declined')
|
||||
messages.success(request,'The selected nominations have been set as declined')
|
||||
elif action == "set_as_pending":
|
||||
nominations.update(state='pending')
|
||||
messages.success(request,'The selected nominations have been set as pending')
|
||||
else:
|
||||
messages.warning(request, "Please, select some nominations to work with")
|
||||
|
||||
filters = {}
|
||||
questionnaire_state = "questionnaire"
|
||||
|
@ -193,7 +193,7 @@ def private_index(request, year):
|
|||
'selected_position': selected_position and int(selected_position) or None,
|
||||
'selected': 'index',
|
||||
'is_chair': is_chair,
|
||||
'message': message}, RequestContext(request))
|
||||
}, RequestContext(request))
|
||||
|
||||
|
||||
@role_required("Nomcom Chair", "Nomcom Advisor")
|
||||
|
@ -201,6 +201,16 @@ def send_reminder_mail(request, year, type):
|
|||
nomcom = get_nomcom_by_year(year)
|
||||
nomcom_template_path = '/nomcom/%s/' % nomcom.group.acronym
|
||||
|
||||
has_publickey = nomcom.public_key and True or False
|
||||
if not has_publickey:
|
||||
messages.warning(request, "This Nomcom does not yet have a public key.")
|
||||
nomcom_ready = False
|
||||
elif nomcom.group.state_id != 'active':
|
||||
messages.warning(request, "This Nomcom is not active.")
|
||||
nomcom_ready = False
|
||||
else:
|
||||
nomcom_ready = True
|
||||
|
||||
if type=='accept':
|
||||
interesting_state = 'pending'
|
||||
mail_path = nomcom_template_path + NOMINEE_ACCEPT_REMINDER_TEMPLATE
|
||||
|
@ -228,19 +238,19 @@ def send_reminder_mail(request, year, type):
|
|||
|
||||
mail_template = DBTemplate.objects.filter(group=nomcom.group, path=mail_path)
|
||||
mail_template = mail_template and mail_template[0] or None
|
||||
message = None
|
||||
|
||||
if request.method == 'POST':
|
||||
if request.method == 'POST' and nomcom_ready:
|
||||
selected_nominees = request.POST.getlist('selected')
|
||||
selected_nominees = nominees.filter(id__in=selected_nominees)
|
||||
if selected_nominees:
|
||||
addrs = send_reminder_to_nominees(selected_nominees,type)
|
||||
if addrs:
|
||||
message = ('success', 'A copy of "%s" has been sent to %s'%(mail_template.title,", ".join(addrs)))
|
||||
messages.success(request, 'A copy of "%s" has been sent to %s'%(mail_template.title,", ".join(addrs)))
|
||||
else:
|
||||
message = ('warning', 'No messages were sent.')
|
||||
messages.warning(request, 'No messages were sent.')
|
||||
else:
|
||||
message = ('warning', "Please, select at least one nominee")
|
||||
messages.warning(request, "Please, select at least one nominee")
|
||||
|
||||
return render_to_response('nomcom/send_reminder_mail.html',
|
||||
{'nomcom': nomcom,
|
||||
'year': year,
|
||||
|
@ -249,27 +259,33 @@ def send_reminder_mail(request, year, type):
|
|||
'selected': selected_tab,
|
||||
'reminder_description': reminder_description,
|
||||
'state_description': state_description,
|
||||
'message': message}, RequestContext(request))
|
||||
'is_chair_task' : True,
|
||||
}, RequestContext(request))
|
||||
|
||||
|
||||
@role_required("Nomcom Chair", "Nomcom Advisor")
|
||||
def private_merge(request, year):
|
||||
nomcom = get_nomcom_by_year(year)
|
||||
message = None
|
||||
if request.method == 'POST':
|
||||
form = MergeForm(request.POST, nomcom=nomcom)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
message = ('success', 'The emails have been unified')
|
||||
if nomcom.group.state_id != 'active':
|
||||
messages.warning(request, "This Nomcom is not active.")
|
||||
form = None
|
||||
else:
|
||||
form = MergeForm(nomcom=nomcom)
|
||||
if request.method == 'POST':
|
||||
form = MergeForm(request.POST, nomcom=nomcom )
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
messages.success(request, 'A merge request has been sent to the secretariat.')
|
||||
return redirect('nomcom_private_index',year=year)
|
||||
else:
|
||||
form = MergeForm(nomcom=nomcom)
|
||||
|
||||
return render_to_response('nomcom/private_merge.html',
|
||||
{'nomcom': nomcom,
|
||||
'year': year,
|
||||
'form': form,
|
||||
'message': message,
|
||||
'selected': 'merge'}, RequestContext(request))
|
||||
'selected': 'merge',
|
||||
'is_chair_task' : True,
|
||||
}, RequestContext(request))
|
||||
|
||||
|
||||
def requirements(request, year):
|
||||
|
@ -294,15 +310,24 @@ def questionnaires(request, year):
|
|||
|
||||
@login_required
|
||||
def public_nominate(request, year):
|
||||
return nominate(request, year, True)
|
||||
return nominate(request=request, year=year, public=True, newperson=False)
|
||||
|
||||
|
||||
@role_required("Nomcom")
|
||||
def private_nominate(request, year):
|
||||
return nominate(request, year, False)
|
||||
return nominate(request=request, year=year, public=False, newperson=False)
|
||||
|
||||
@login_required
|
||||
def public_nominate_newperson(request, year):
|
||||
return nominate(request=request, year=year, public=True, newperson=True)
|
||||
|
||||
|
||||
def nominate(request, year, public):
|
||||
@role_required("Nomcom")
|
||||
def private_nominate_newperson(request, year):
|
||||
return nominate(request=request, year=year, public=False, newperson=True)
|
||||
|
||||
|
||||
def nominate(request, year, public, newperson):
|
||||
nomcom = get_nomcom_by_year(year)
|
||||
has_publickey = nomcom.public_key and True or False
|
||||
if public:
|
||||
|
@ -311,31 +336,43 @@ def nominate(request, year, public):
|
|||
template = 'nomcom/private_nominate.html'
|
||||
|
||||
if not has_publickey:
|
||||
message = ('warning', "This Nomcom is not yet accepting nominations")
|
||||
messages.warning(request, "This Nomcom is not yet accepting nominations")
|
||||
return render_to_response(template,
|
||||
{'message': message,
|
||||
'nomcom': nomcom,
|
||||
{'nomcom': nomcom,
|
||||
'year': year,
|
||||
'selected': 'nominate'}, RequestContext(request))
|
||||
|
||||
if nomcom.group.state_id == 'conclude':
|
||||
messages.warning(request, "Nominations to this Nomcom are closed.")
|
||||
return render_to_response(template,
|
||||
{'nomcom': nomcom,
|
||||
'year': year,
|
||||
'selected': 'nominate'}, RequestContext(request))
|
||||
|
||||
message = None
|
||||
if request.method == 'POST':
|
||||
form = NominateForm(data=request.POST, nomcom=nomcom, user=request.user, public=public)
|
||||
if newperson:
|
||||
form = NominateNewPersonForm(data=request.POST, nomcom=nomcom, user=request.user, public=public)
|
||||
else:
|
||||
form = NominateForm(data=request.POST, nomcom=nomcom, user=request.user, public=public)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
message = ('success', 'Your nomination has been registered. Thank you for the nomination.')
|
||||
form = NominateForm(nomcom=nomcom, user=request.user, public=public)
|
||||
messages.success(request, 'Your nomination has been registered. Thank you for the nomination.')
|
||||
if newperson:
|
||||
return redirect('nomcom_%s_nominate' % ('public' if public else 'private'), year=year)
|
||||
else:
|
||||
form = NominateForm(nomcom=nomcom, user=request.user, public=public)
|
||||
else:
|
||||
form = NominateForm(nomcom=nomcom, user=request.user, public=public)
|
||||
if newperson:
|
||||
form = NominateNewPersonForm(nomcom=nomcom, user=request.user, public=public)
|
||||
else:
|
||||
form = NominateForm(nomcom=nomcom, user=request.user, public=public)
|
||||
|
||||
return render_to_response(template,
|
||||
{'form': form,
|
||||
'message': message,
|
||||
'nomcom': nomcom,
|
||||
'year': year,
|
||||
'selected': 'nominate'}, RequestContext(request))
|
||||
|
||||
|
||||
@login_required
|
||||
def public_feedback(request, year):
|
||||
return feedback(request, year, True)
|
||||
|
@ -349,55 +386,61 @@ def private_feedback(request, year):
|
|||
def feedback(request, year, public):
|
||||
nomcom = get_nomcom_by_year(year)
|
||||
has_publickey = nomcom.public_key and True or False
|
||||
submit_disabled = True
|
||||
nominee = None
|
||||
position = None
|
||||
selected_nominee = request.GET.get('nominee')
|
||||
selected_position = request.GET.get('position')
|
||||
if selected_nominee and selected_position:
|
||||
nominee = get_object_or_404(Nominee, id=selected_nominee)
|
||||
position = get_object_or_404(Position, id=selected_position)
|
||||
submit_disabled = False
|
||||
if nomcom.group.state_id != 'conclude':
|
||||
selected_nominee = request.GET.get('nominee')
|
||||
selected_position = request.GET.get('position')
|
||||
if selected_nominee and selected_position:
|
||||
nominee = get_object_or_404(Nominee, id=selected_nominee)
|
||||
position = get_object_or_404(Position, id=selected_position)
|
||||
|
||||
positions = Position.objects.get_by_nomcom(nomcom=nomcom).opened()
|
||||
|
||||
user_comments = Feedback.objects.filter(nomcom=nomcom,
|
||||
type='comment',
|
||||
author__in=request.user.person.email_set.filter(active='True'))
|
||||
counter = Counter(user_comments.values_list('positions','nominees'))
|
||||
counts = dict()
|
||||
for pos,nom in counter:
|
||||
counts.setdefault(pos,dict())[nom] = counter[(pos,nom)]
|
||||
if public:
|
||||
base_template = "nomcom/nomcom_public_base.html"
|
||||
else:
|
||||
base_template = "nomcom/nomcom_private_base.html"
|
||||
|
||||
if not has_publickey:
|
||||
message = ('warning', "This Nomcom is not yet accepting comments")
|
||||
messages.warning(request, "This Nomcom is not yet accepting comments")
|
||||
return render(request, 'nomcom/feedback.html', {
|
||||
'message': message,
|
||||
'nomcom': nomcom,
|
||||
'year': year,
|
||||
'selected': 'feedback',
|
||||
'counts' : counts,
|
||||
'base_template': base_template
|
||||
})
|
||||
|
||||
message = None
|
||||
if request.method == 'POST':
|
||||
if nominee and position and request.method == 'POST':
|
||||
form = FeedbackForm(data=request.POST,
|
||||
nomcom=nomcom, user=request.user,
|
||||
public=public, position=position, nominee=nominee)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
message = ('success', 'Your feedback has been registered.')
|
||||
messages.success(request, 'Your feedback has been registered.')
|
||||
form = None
|
||||
else:
|
||||
if nominee and position:
|
||||
form = FeedbackForm(nomcom=nomcom, user=request.user, public=public,
|
||||
position=position, nominee=nominee)
|
||||
else:
|
||||
form = FeedbackForm(nomcom=nomcom, user=request.user, public=public,
|
||||
position=position, nominee=nominee)
|
||||
else:
|
||||
form = None
|
||||
|
||||
return render(request, 'nomcom/feedback.html', {
|
||||
'form': form,
|
||||
'message': message,
|
||||
'nomcom': nomcom,
|
||||
'year': year,
|
||||
'positions': positions,
|
||||
'submit_disabled': submit_disabled,
|
||||
'selected': 'feedback',
|
||||
'counts': counts,
|
||||
'base_template': base_template
|
||||
})
|
||||
|
||||
|
@ -406,16 +449,24 @@ def feedback(request, year, public):
|
|||
def private_feedback_email(request, year):
|
||||
nomcom = get_nomcom_by_year(year)
|
||||
has_publickey = nomcom.public_key and True or False
|
||||
message = None
|
||||
template = 'nomcom/private_feedback_email.html'
|
||||
|
||||
if not has_publickey:
|
||||
message = ('warning', "This Nomcom is not yet accepting feedback email")
|
||||
return render_to_response(template,
|
||||
{'message': message,
|
||||
'nomcom': nomcom,
|
||||
'year': year,
|
||||
'selected': 'feedback_email'}, RequestContext(request))
|
||||
messages.warning(request, "This Nomcom is not yet accepting feedback email.")
|
||||
nomcom_ready = False
|
||||
elif nomcom.group.state_id != 'active':
|
||||
messages.warning(request, "This Nomcom is not active, and is not accepting feedback email.")
|
||||
nomcom_ready = False
|
||||
else:
|
||||
nomcom_ready = True
|
||||
|
||||
if not nomcom_ready:
|
||||
return render_to_response(template,
|
||||
{'nomcom': nomcom,
|
||||
'year': year,
|
||||
'selected': 'feedback_email',
|
||||
'is_chair_task' : True,
|
||||
}, RequestContext(request))
|
||||
|
||||
form = FeedbackEmailForm(nomcom=nomcom)
|
||||
|
||||
|
@ -425,38 +476,44 @@ def private_feedback_email(request, year):
|
|||
if form.is_valid():
|
||||
form.save()
|
||||
form = FeedbackEmailForm(nomcom=nomcom)
|
||||
message = ('success', 'The feedback email has been registered.')
|
||||
messages.success(request, 'The feedback email has been registered.')
|
||||
|
||||
return render_to_response(template,
|
||||
{'form': form,
|
||||
'message': message,
|
||||
'nomcom': nomcom,
|
||||
'year': year,
|
||||
'selected': 'feedback_email'}, RequestContext(request))
|
||||
|
||||
|
||||
@role_required("Nomcom Chair", "Nomcom Advisor")
|
||||
def private_questionnaire(request, year):
|
||||
nomcom = get_nomcom_by_year(year)
|
||||
has_publickey = nomcom.public_key and True or False
|
||||
message = None
|
||||
questionnaire_response = None
|
||||
template = 'nomcom/private_questionnaire.html'
|
||||
|
||||
if not has_publickey:
|
||||
message = ('warning', "This Nomcom is not yet accepting questionnaires")
|
||||
return render_to_response(template,
|
||||
{'message': message,
|
||||
'nomcom': nomcom,
|
||||
'year': year,
|
||||
'selected': 'questionnaire'}, RequestContext(request))
|
||||
messages.warning(request, "This Nomcom is not yet accepting questionnaires.")
|
||||
nomcom_ready = False
|
||||
elif nomcom.group.state_id != 'active':
|
||||
messages.warning(request, "This Nomcom is not active, and is not accepting questionnaires.")
|
||||
nomcom_ready = False
|
||||
else:
|
||||
nomcom_ready = True
|
||||
|
||||
if not nomcom_ready:
|
||||
return render_to_response(template,
|
||||
{'nomcom': nomcom,
|
||||
'year': year,
|
||||
'selected': 'questionnaire',
|
||||
'is_chair_task' : True,
|
||||
}, RequestContext(request))
|
||||
|
||||
if request.method == 'POST':
|
||||
form = QuestionnaireForm(data=request.POST,
|
||||
nomcom=nomcom, user=request.user)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
message = ('success', 'The questionnaire response has been registered.')
|
||||
messages.success(request, 'The questionnaire response has been registered.')
|
||||
questionnaire_response = form.cleaned_data['comments']
|
||||
form = QuestionnaireForm(nomcom=nomcom, user=request.user)
|
||||
else:
|
||||
|
@ -465,7 +522,6 @@ def private_questionnaire(request, year):
|
|||
return render_to_response(template,
|
||||
{'form': form,
|
||||
'questionnaire_response': questionnaire_response,
|
||||
'message': message,
|
||||
'nomcom': nomcom,
|
||||
'year': year,
|
||||
'selected': 'questionnaire'}, RequestContext(request))
|
||||
|
@ -478,35 +534,52 @@ def process_nomination_status(request, year, nominee_position_id, state, date, h
|
|||
expiration_days = getattr(settings, 'DAYS_TO_EXPIRE_NOMINATION_LINK', None)
|
||||
if expiration_days:
|
||||
request_date = datetime.date(int(date[:4]), int(date[4:6]), int(date[6:]))
|
||||
if datetime.date.today() > (request_date + datetime.timedelta(days=settings.DAYS_TO_EXPIRE_REGISTRATION_LINK)):
|
||||
if datetime.date.today() > (request_date + datetime.timedelta(days=settings.DAYS_TO_EXPIRE_NOMINATION_LINK)):
|
||||
return HttpResponseForbidden("Link expired")
|
||||
|
||||
need_confirmation = True
|
||||
nomcom = get_nomcom_by_year(year)
|
||||
if nomcom.group.state_id == 'conclude':
|
||||
return HttpResponseForbidden("This nomcom is concluded.")
|
||||
nominee_position = get_object_or_404(NomineePosition, id=nominee_position_id)
|
||||
if nominee_position.state.slug != "pending":
|
||||
return HttpResponseForbidden("The nomination already was %s" % nominee_position.state)
|
||||
|
||||
state = get_object_or_404(NomineePositionStateName, slug=state)
|
||||
message = ('warning',
|
||||
("Click on 'Save' to set the state of your nomination to %s to %s (this"+
|
||||
"is not a final commitment - you can notify us later if you need to change this)") %
|
||||
(nominee_position.position.name, state.name))
|
||||
messages.info(request, "Click on 'Save' to set the state of your nomination to %s to %s (this is not a final commitment - you can notify us later if you need to change this)." % (nominee_position.position.name, state.name))
|
||||
if request.method == 'POST':
|
||||
nominee_position.state = state
|
||||
nominee_position.save()
|
||||
need_confirmation = False
|
||||
message = message = ('success', 'Your nomination on %s has been set as %s' % (nominee_position.position.name,
|
||||
state.name))
|
||||
|
||||
form = NominationResponseCommentForm(request.POST)
|
||||
if form.is_valid():
|
||||
nominee_position.state = state
|
||||
nominee_position.save()
|
||||
need_confirmation = False
|
||||
if form.cleaned_data['comments']:
|
||||
# This Feedback object is of type comment instead of nomina in order to not
|
||||
# make answering "who nominated themselves" harder.
|
||||
who = request.user
|
||||
if isinstance(who,AnonymousUser):
|
||||
who = None
|
||||
f = Feedback.objects.create(nomcom = nomcom,
|
||||
author = nominee_position.nominee.email,
|
||||
subject = '%s nomination %s'%(nominee_position.nominee.name(),state),
|
||||
comments = form.cleaned_data['comments'],
|
||||
type_id = 'comment',
|
||||
user = who,
|
||||
)
|
||||
f.positions.add(nominee_position.position)
|
||||
f.nominees.add(nominee_position.nominee)
|
||||
|
||||
messages.success(request, 'Your nomination on %s has been set as %s' % (nominee_position.position.name, state.name))
|
||||
else:
|
||||
form = NominationResponseCommentForm()
|
||||
return render_to_response('nomcom/process_nomination_status.html',
|
||||
{'message': message,
|
||||
'nomcom': nomcom,
|
||||
{'nomcom': nomcom,
|
||||
'year': year,
|
||||
'nominee_position': nominee_position,
|
||||
'state': state,
|
||||
'need_confirmation': need_confirmation,
|
||||
'selected': 'feedback'}, RequestContext(request))
|
||||
'selected': 'feedback',
|
||||
'form': form }, RequestContext(request))
|
||||
|
||||
|
||||
@role_required("Nomcom")
|
||||
|
@ -521,10 +594,36 @@ def view_feedback(request, year):
|
|||
feedback_types.append(ft)
|
||||
else:
|
||||
independent_feedback_types.append(ft)
|
||||
nominees_feedback = {}
|
||||
nominees_feedback = []
|
||||
|
||||
def nominee_staterank(nominee):
|
||||
states=nominee.nomineeposition_set.values_list('state_id',flat=True)
|
||||
if 'accepted' in states:
|
||||
return 0
|
||||
elif 'pending' in states:
|
||||
return 1
|
||||
else:
|
||||
return 2
|
||||
|
||||
for nominee in nominees:
|
||||
nominee_feedback = [(ft.name, nominee.feedback_set.by_type(ft.slug).count()) for ft in feedback_types]
|
||||
nominees_feedback.update({nominee: nominee_feedback})
|
||||
nominee.staterank = nominee_staterank(nominee)
|
||||
|
||||
sorted_nominees = sorted(nominees,key=lambda x:x.staterank)
|
||||
|
||||
for nominee in sorted_nominees:
|
||||
last_seen = FeedbackLastSeen.objects.filter(reviewer=request.user.person,nominee=nominee).first()
|
||||
nominee_feedback = []
|
||||
for ft in feedback_types:
|
||||
qs = nominee.feedback_set.by_type(ft.slug)
|
||||
count = qs.count()
|
||||
if not count:
|
||||
newflag = False
|
||||
elif not last_seen:
|
||||
newflag = True
|
||||
else:
|
||||
newflag = qs.filter(time__gt=last_seen.time).exists()
|
||||
nominee_feedback.append( (ft.name,count,newflag) )
|
||||
nominees_feedback.append( {'nominee':nominee, 'feedback':nominee_feedback} )
|
||||
independent_feedback = [ft.feedback_set.get_by_nomcom(nomcom).count() for ft in independent_feedback_types]
|
||||
|
||||
return render_to_response('nomcom/view_feedback.html',
|
||||
|
@ -542,33 +641,17 @@ def view_feedback(request, year):
|
|||
@nomcom_private_key_required
|
||||
def view_feedback_pending(request, year):
|
||||
nomcom = get_nomcom_by_year(year)
|
||||
if nomcom.group.state_id == 'conclude':
|
||||
return HttpResponseForbidden("This nomcom is concluded.")
|
||||
extra_ids = None
|
||||
message = None
|
||||
for message in messages.get_messages(request):
|
||||
message = ('success', message.message)
|
||||
FeedbackFormSet = modelformset_factory(Feedback,
|
||||
form=PendingFeedbackForm,
|
||||
extra=0)
|
||||
feedbacks = Feedback.objects.filter(type__isnull=True, nomcom=nomcom)
|
||||
|
||||
try:
|
||||
default_type = FeedbackTypeName.objects.get(slug=settings.DEFAULT_FEEDBACK_TYPE)
|
||||
except FeedbackTypeName.DoesNotExist:
|
||||
default_type = None
|
||||
|
||||
extra_step = False
|
||||
if request.method == 'POST' and request.POST.get('move_to_default'):
|
||||
formset = FeedbackFormSet(request.POST)
|
||||
if formset.is_valid():
|
||||
for form in formset.forms:
|
||||
form.set_nomcom(nomcom, request.user)
|
||||
form.move_to_default()
|
||||
formset = FeedbackFormSet(queryset=feedbacks)
|
||||
for form in formset.forms:
|
||||
form.set_nomcom(nomcom, request.user)
|
||||
messages.success(request, 'Feedback saved')
|
||||
return redirect('nomcom_view_feedback_pending', year=year)
|
||||
elif request.method == 'POST' and request.POST.get('end'):
|
||||
if request.method == 'POST' and request.POST.get('end'):
|
||||
extra_ids = request.POST.get('extra_ids', None)
|
||||
extra_step = True
|
||||
formset = FullFeedbackFormSet(request.POST)
|
||||
|
@ -623,7 +706,7 @@ def view_feedback_pending(request, year):
|
|||
for form in formset.forms:
|
||||
form.set_nomcom(nomcom, request.user, extra)
|
||||
if moved:
|
||||
message = ('success', '%s messages classified. You must enter more information for the following feedback.' % moved)
|
||||
messages.success(request, '%s messages classified. You must enter more information for the following feedback.' % moved)
|
||||
else:
|
||||
messages.success(request, 'Feedback saved')
|
||||
return redirect('nomcom_view_feedback_pending', year=year)
|
||||
|
@ -644,13 +727,13 @@ def view_feedback_pending(request, year):
|
|||
{'year': year,
|
||||
'selected': 'feedback_pending',
|
||||
'formset': formset,
|
||||
'message': message,
|
||||
'extra_step': extra_step,
|
||||
'default_type': default_type,
|
||||
'type_dict': type_dict,
|
||||
'extra_ids': extra_ids,
|
||||
'types': FeedbackTypeName.objects.all().order_by('pk'),
|
||||
'nomcom': nomcom}, RequestContext(request))
|
||||
'nomcom': nomcom,
|
||||
'is_chair_task' : True,
|
||||
}, RequestContext(request))
|
||||
|
||||
|
||||
@role_required("Nomcom")
|
||||
|
@ -676,11 +759,19 @@ def view_feedback_nominee(request, year, nominee_id):
|
|||
nominee = get_object_or_404(Nominee, id=nominee_id)
|
||||
feedback_types = FeedbackTypeName.objects.filter(slug__in=settings.NOMINEE_FEEDBACK_TYPES)
|
||||
|
||||
last_seen = FeedbackLastSeen.objects.filter(reviewer=request.user.person,nominee=nominee).first()
|
||||
last_seen_time = (last_seen and last_seen.time) or datetime.datetime(year=1,month=1,day=1)
|
||||
if last_seen:
|
||||
last_seen.save()
|
||||
else:
|
||||
FeedbackLastSeen.objects.create(reviewer=request.user.person,nominee=nominee)
|
||||
|
||||
return render_to_response('nomcom/view_feedback_nominee.html',
|
||||
{'year': year,
|
||||
'selected': 'view_feedback',
|
||||
'nominee': nominee,
|
||||
'feedback_types': feedback_types,
|
||||
'last_seen_time' : last_seen_time,
|
||||
'nomcom': nomcom}, RequestContext(request))
|
||||
|
||||
|
||||
|
@ -688,14 +779,14 @@ def view_feedback_nominee(request, year, nominee_id):
|
|||
def edit_nominee(request, year, nominee_id):
|
||||
nomcom = get_nomcom_by_year(year)
|
||||
nominee = get_object_or_404(Nominee, id=nominee_id)
|
||||
message = None
|
||||
|
||||
if request.method == 'POST':
|
||||
form = EditNomineeForm(request.POST,
|
||||
instance=nominee)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
message = ('success', 'The nominee has been changed')
|
||||
messages.success(request, 'The nomination address for %s has been changed to %s'%(nominee.name(),nominee.email.address))
|
||||
return redirect('nomcom_private_index', year=year)
|
||||
else:
|
||||
form = EditNomineeForm(instance=nominee)
|
||||
|
||||
|
@ -704,8 +795,9 @@ def edit_nominee(request, year, nominee_id):
|
|||
'selected': 'index',
|
||||
'nominee': nominee,
|
||||
'form': form,
|
||||
'message': message,
|
||||
'nomcom': nomcom}, RequestContext(request))
|
||||
'nomcom': nomcom,
|
||||
'is_chair_task' : True,
|
||||
}, RequestContext(request))
|
||||
|
||||
|
||||
@role_required("Nomcom Chair", "Nomcom Advisor")
|
||||
|
@ -713,14 +805,18 @@ def edit_nomcom(request, year):
|
|||
nomcom = get_nomcom_by_year(year)
|
||||
|
||||
if nomcom.public_key:
|
||||
message = ('warning', 'Previous data will remain encrypted with the old key')
|
||||
messages.warning(request, 'Previous data will remain encrypted with the old key')
|
||||
else:
|
||||
message = ('warning', 'The nomcom has not a public key yet')
|
||||
messages.warning(request, 'This Nomcom does not yet have a public key')
|
||||
|
||||
ReminderDateInlineFormSet = inlineformset_factory(parent_model=NomCom,
|
||||
model=ReminderDates,
|
||||
form=ReminderDatesForm)
|
||||
if request.method == 'POST':
|
||||
|
||||
if nomcom.group.state_id=='conclude':
|
||||
return HttpResponseForbidden('This nomcom is closed.')
|
||||
|
||||
formset = ReminderDateInlineFormSet(request.POST, instance=nomcom)
|
||||
form = EditNomcomForm(request.POST,
|
||||
request.FILES,
|
||||
|
@ -729,7 +825,7 @@ def edit_nomcom(request, year):
|
|||
form.save()
|
||||
formset.save()
|
||||
formset = ReminderDateInlineFormSet(instance=nomcom)
|
||||
message = ('success', 'The nomcom has been changed')
|
||||
messages.success(request, 'The nomcom has been changed')
|
||||
else:
|
||||
formset = ReminderDateInlineFormSet(instance=nomcom)
|
||||
form = EditNomcomForm(instance=nomcom)
|
||||
|
@ -738,26 +834,12 @@ def edit_nomcom(request, year):
|
|||
{'form': form,
|
||||
'formset': formset,
|
||||
'nomcom': nomcom,
|
||||
'message': message,
|
||||
'year': year,
|
||||
'selected': 'edit_nomcom'}, RequestContext(request))
|
||||
'selected': 'edit_nomcom',
|
||||
'is_chair_task' : True,
|
||||
}, RequestContext(request))
|
||||
|
||||
|
||||
@role_required("Nomcom Chair", "Nomcom Advisor")
|
||||
def delete_nomcom(request, year):
|
||||
nomcom = get_nomcom_by_year(year)
|
||||
|
||||
if request.method == 'POST':
|
||||
nomcom.delete()
|
||||
messages.success(request, "Deleted NomCom data")
|
||||
return redirect('nomcom_deleted')
|
||||
|
||||
return render(request, 'nomcom/delete_nomcom.html', {
|
||||
'year': year,
|
||||
'selected': 'edit_nomcom',
|
||||
'nomcom': nomcom,
|
||||
})
|
||||
|
||||
|
||||
@role_required("Nomcom Chair", "Nomcom Advisor")
|
||||
def list_templates(request, year):
|
||||
|
@ -770,7 +852,9 @@ def list_templates(request, year):
|
|||
'positions': positions,
|
||||
'year': year,
|
||||
'selected': 'edit_templates',
|
||||
'nomcom': nomcom}, RequestContext(request))
|
||||
'nomcom': nomcom,
|
||||
'is_chair_task' : True,
|
||||
}, RequestContext(request))
|
||||
|
||||
|
||||
@role_required("Nomcom Chair", "Nomcom Advisor")
|
||||
|
@ -778,29 +862,44 @@ def edit_template(request, year, template_id):
|
|||
nomcom = get_nomcom_by_year(year)
|
||||
return_url = request.META.get('HTTP_REFERER', None)
|
||||
|
||||
return template_edit(request, nomcom.group.acronym, template_id,
|
||||
base_template='nomcom/edit_template.html',
|
||||
formclass=NomComTemplateForm,
|
||||
extra_context={'year': year,
|
||||
'return_url': return_url,
|
||||
'nomcom': nomcom})
|
||||
if nomcom.group.state_id=='conclude':
|
||||
return template_show(request, nomcom.group.acronym, template_id,
|
||||
base_template='nomcom/show_template.html',
|
||||
extra_context={'year': year,
|
||||
'return_url': return_url,
|
||||
'nomcom': nomcom,
|
||||
'is_chair_task' : True,
|
||||
})
|
||||
else:
|
||||
return template_edit(request, nomcom.group.acronym, template_id,
|
||||
base_template='nomcom/edit_template.html',
|
||||
formclass=NomComTemplateForm,
|
||||
extra_context={'year': year,
|
||||
'return_url': return_url,
|
||||
'nomcom': nomcom,
|
||||
'is_chair_task' : True,
|
||||
})
|
||||
|
||||
|
||||
@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,
|
||||
'year': year,
|
||||
'selected': 'edit_positions',
|
||||
'nomcom': nomcom}, RequestContext(request))
|
||||
'nomcom': nomcom,
|
||||
'is_chair_task' : True,
|
||||
}, RequestContext(request))
|
||||
|
||||
|
||||
@role_required("Nomcom Chair", "Nomcom Advisor")
|
||||
def remove_position(request, year, position_id):
|
||||
nomcom = get_nomcom_by_year(year)
|
||||
if nomcom.group.state_id=='conclude':
|
||||
return HttpResponseForbidden('This nomcom is closed.')
|
||||
try:
|
||||
position = nomcom.position_set.get(id=position_id)
|
||||
except Position.DoesNotExist:
|
||||
|
@ -812,12 +911,18 @@ def remove_position(request, year, position_id):
|
|||
return render_to_response('nomcom/remove_position.html',
|
||||
{'year': year,
|
||||
'position': position,
|
||||
'nomcom': nomcom}, RequestContext(request))
|
||||
'nomcom': nomcom,
|
||||
'is_chair_task' : True,
|
||||
}, RequestContext(request))
|
||||
|
||||
|
||||
@role_required("Nomcom Chair", "Nomcom Advisor")
|
||||
def edit_position(request, year, position_id=None):
|
||||
nomcom = get_nomcom_by_year(year)
|
||||
|
||||
if nomcom.group.state_id=='conclude':
|
||||
return HttpResponseForbidden('This nomcom is closed.')
|
||||
|
||||
if position_id:
|
||||
try:
|
||||
position = nomcom.position_set.get(id=position_id)
|
||||
|
@ -838,4 +943,10 @@ def edit_position(request, year, position_id=None):
|
|||
{'form': form,
|
||||
'position': position,
|
||||
'year': year,
|
||||
'nomcom': nomcom}, RequestContext(request))
|
||||
'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})
|
||||
|
|
58
ietf/person/factories.py
Normal file
58
ietf/person/factories.py
Normal file
|
@ -0,0 +1,58 @@
|
|||
import factory
|
||||
import faker
|
||||
|
||||
from unidecode import unidecode
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from ietf.person.models import Person, Alias, Email
|
||||
|
||||
fake = faker.Factory.create()
|
||||
|
||||
class UserFactory(factory.DjangoModelFactory):
|
||||
class Meta:
|
||||
model = User
|
||||
django_get_or_create = ('username',)
|
||||
|
||||
first_name = factory.Faker('first_name')
|
||||
last_name = factory.Faker('last_name')
|
||||
email = factory.LazyAttribute(lambda u: '%s.%s@%s'%(u.first_name,u.last_name,fake.domain_name()))
|
||||
username = factory.LazyAttribute(lambda u: u.email)
|
||||
|
||||
@factory.post_generation
|
||||
def set_password(self, create, extracted, **kwargs):
|
||||
self.set_password( '%s+password' % self.username )
|
||||
|
||||
class PersonFactory(factory.DjangoModelFactory):
|
||||
class Meta:
|
||||
model = Person
|
||||
|
||||
user = factory.SubFactory(UserFactory)
|
||||
name = factory.LazyAttribute(lambda p: '%s %s'%(p.user.first_name,p.user.last_name))
|
||||
ascii = factory.LazyAttribute(lambda p: unidecode(p.name))
|
||||
|
||||
@factory.post_generation
|
||||
def default_aliases(self, create, extracted, **kwargs):
|
||||
make_alias = getattr(AliasFactory, 'create' if create else 'build')
|
||||
make_alias(person=self,name=self.name)
|
||||
make_alias(person=self,name=self.ascii)
|
||||
|
||||
@factory.post_generation
|
||||
def default_emails(self, create, extracted, **kwargs):
|
||||
make_email = getattr(EmailFactory, 'create' if create else 'build')
|
||||
make_email(person=self,address=self.user.email)
|
||||
|
||||
class AliasFactory(factory.DjangoModelFactory):
|
||||
class Meta:
|
||||
model = Alias
|
||||
django_get_or_create = ('name',)
|
||||
|
||||
name = factory.Faker('name')
|
||||
|
||||
class EmailFactory(factory.DjangoModelFactory):
|
||||
class Meta:
|
||||
model = Email
|
||||
django_get_or_create = ('address',)
|
||||
|
||||
address = '%s.%s@%s' % (fake.first_name(),fake.last_name(),fake.domain_name())
|
||||
active = True
|
||||
primary = False
|
|
@ -1,5 +1,7 @@
|
|||
import json
|
||||
|
||||
from collections import Counter
|
||||
|
||||
from django.utils.html import escape
|
||||
from django import forms
|
||||
from django.core.urlresolvers import reverse as urlreverse
|
||||
|
@ -12,7 +14,19 @@ def select2_id_name_json(objs):
|
|||
def format_email(e):
|
||||
return escape(u"%s <%s>" % (e.person.name, e.address))
|
||||
def format_person(p):
|
||||
return escape(p.name)
|
||||
if p.name_count > 1:
|
||||
return escape('%s (%s)' % (p.name,p.email().address))
|
||||
else:
|
||||
return escape(p.name)
|
||||
|
||||
if objs and isinstance(objs[0], Email):
|
||||
formatter = format_email
|
||||
else:
|
||||
formatter = format_person
|
||||
c = Counter([p.name for p in objs])
|
||||
for p in objs:
|
||||
p.name_count = c[p.name]
|
||||
|
||||
|
||||
formatter = format_email if objs and isinstance(objs[0], Email) else format_person
|
||||
|
||||
|
|
|
@ -59,10 +59,13 @@ class PersonInfo(models.Model):
|
|||
if e:
|
||||
return e[0]
|
||||
return None
|
||||
def email_address(self):
|
||||
def email(self):
|
||||
e = self.email_set.filter(primary=True).first()
|
||||
if not e:
|
||||
e = self.email_set.filter(active=True).order_by("-time").first()
|
||||
return e
|
||||
def email_address(self):
|
||||
e = self.email()
|
||||
if e:
|
||||
return e.address
|
||||
else:
|
||||
|
|
88
ietf/person/utils.py
Executable file
88
ietf/person/utils.py
Executable file
|
@ -0,0 +1,88 @@
|
|||
import pprint
|
||||
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.models import User
|
||||
from ietf.person.models import Person
|
||||
|
||||
def merge_persons(source,target,stream):
|
||||
|
||||
# merge emails
|
||||
for email in source.email_set.all():
|
||||
print >>stream, "Merging email: {}".format(email.address)
|
||||
email.person = target
|
||||
email.save()
|
||||
|
||||
# merge aliases
|
||||
target_aliases = [ a.name for a in target.alias_set.all() ]
|
||||
for alias in source.alias_set.all():
|
||||
if alias.name in target_aliases:
|
||||
alias.delete()
|
||||
else:
|
||||
print >>stream,"Merging alias: {}".format(alias.name)
|
||||
alias.person = target
|
||||
alias.save()
|
||||
|
||||
# merge DocEvents
|
||||
for docevent in source.docevent_set.all():
|
||||
docevent.by = target
|
||||
docevent.save()
|
||||
|
||||
# merge SubmissionEvents
|
||||
for subevent in source.submissionevent_set.all():
|
||||
subevent.by = target
|
||||
subevent.save()
|
||||
|
||||
# merge Messages
|
||||
for message in source.message_set.all():
|
||||
message.by = target
|
||||
message.save()
|
||||
|
||||
# merge Constraints
|
||||
for constraint in source.constraint_set.all():
|
||||
constraint.person = target
|
||||
constraint.save()
|
||||
|
||||
# merge Roles
|
||||
for role in source.role_set.all():
|
||||
role.person = target
|
||||
role.save()
|
||||
|
||||
# merge Nominees
|
||||
for nominee in source.nominee_set.all():
|
||||
target_nominee = target.nominee_set.get(nomcom=nominee.nomcom)
|
||||
if not target_nominee:
|
||||
target_nominee = target.nominee_set.create(nomcom=nominee.nomcom, email=target.email())
|
||||
nominee.nomination_set.all().update(nominee=target_nominee)
|
||||
for fb in nominee.feedback_set.all():
|
||||
fb.nominees.remove(nominee)
|
||||
fb.nominees.add(target_nominee)
|
||||
for np in nominee.nomineeposition_set.all():
|
||||
existing_target_np = target_nominee.nomineeposition_set.filter(position=np.position).first()
|
||||
if existing_target_np:
|
||||
if existing_target_np.state.slug=='pending':
|
||||
existing_target_np.state = np.state
|
||||
existing_target_np.save()
|
||||
np.delete()
|
||||
else:
|
||||
np.nominee=target_nominee
|
||||
np.save()
|
||||
nominee.delete()
|
||||
|
||||
# check for any remaining relationships and delete if none
|
||||
objs = [source]
|
||||
opts = Person._meta
|
||||
user = User.objects.filter(is_superuser=True).first()
|
||||
admin_site = admin.site
|
||||
using = 'default'
|
||||
|
||||
deletable_objects, perms_needed, protected = admin.utils.get_deleted_objects(
|
||||
objs, opts, user, admin_site, using)
|
||||
|
||||
if len(deletable_objects) > 1:
|
||||
print >>stream, "Not Deleting Person: {}({})".format(source.ascii,source.pk)
|
||||
print >>stream, "Related objects remain:"
|
||||
pprint.pprint(deletable_objects[1],stream=stream)
|
||||
|
||||
else:
|
||||
print >>stream, "Deleting Person: {}({})".format(source.ascii,source.pk)
|
||||
source.delete()
|
|
@ -455,7 +455,6 @@ NOMCOM_PUBLIC_KEYS_DIR = '/a/www/nomcom/public_keys/'
|
|||
NOMCOM_FROM_EMAIL = 'nomcom-chair@ietf.org'
|
||||
OPENSSL_COMMAND = '/usr/bin/openssl'
|
||||
DAYS_TO_EXPIRE_NOMINATION_LINK = ''
|
||||
DEFAULT_FEEDBACK_TYPE = 'offtopic'
|
||||
NOMINEE_FEEDBACK_TYPES = ['comment', 'questio', 'nomina']
|
||||
|
||||
# ID Submission Tool settings
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
# ./manage.py test --settings=settings_sqlitetest doc.ChangeStateTestCase
|
||||
#
|
||||
|
||||
import os
|
||||
from settings import * # pyflakes:ignore
|
||||
|
||||
# Workaround to avoid spending minutes stepping through the migrations in
|
||||
|
@ -38,4 +39,5 @@ DATABASES = {
|
|||
|
||||
if TEST_CODE_COVERAGE_CHECKER and not TEST_CODE_COVERAGE_CHECKER._started:
|
||||
TEST_CODE_COVERAGE_CHECKER.start()
|
||||
|
||||
|
||||
NOMCOM_PUBLIC_KEYS_DIR=os.path.abspath("tmp-nomcom-public-keys-dir")
|
||||
|
|
|
@ -324,7 +324,7 @@ class SubmitTests(TestCase):
|
|||
self.assertTrue((u"I-D Action: %s" % name) in outbox[-3]["Subject"])
|
||||
self.assertTrue((u"I-D Action: %s" % name) in draft.message_set.order_by("-time")[0].subject)
|
||||
self.assertTrue("Author Name" in unicode(outbox[-3]))
|
||||
self.assertTrue("ietf-announce@" in outbox[-3]['To'])
|
||||
self.assertTrue("i-d-announce@" in outbox[-3]['To'])
|
||||
self.assertTrue("New Version Notification" in outbox[-2]["Subject"])
|
||||
self.assertTrue(name in unicode(outbox[-2]))
|
||||
self.assertTrue("mars" in unicode(outbox[-2]))
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<!DOCTYPE html> {% load ietf_filters staticfiles %}
|
||||
{# Copyright The IETF Trust 2015, All Rights Reserved #}
|
||||
{% load origin %}{% origin %}
|
||||
{% load bootstrap3 %}
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
@ -83,15 +84,7 @@
|
|||
</nav>
|
||||
{% endwith %}
|
||||
<div class="container-fluid">
|
||||
{% if messages %}
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
{% for message in messages %}
|
||||
<p class="alert alert-info {% if message.tags %}{{ message.tags }}{% endif %}">{{ message }}</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% bootstrap_messages %}
|
||||
{% if request.COOKIES.left_menu != "off" and not hide_menu %} {# ugly hack for the more or less unported meeting agenda edit pages #}
|
||||
<div class="row">
|
||||
<div class="col-md-2 visible-md visible-lg leftmenu">
|
||||
|
|
98
ietf/templates/nomcom/chair_help.html
Normal file
98
ietf/templates/nomcom/chair_help.html
Normal file
|
@ -0,0 +1,98 @@
|
|||
{% 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 %}
|
||||
|
||||
<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 %}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
{% extends "base.html" %}
|
||||
{# Copyright The IETF Trust 2015, All Rights Reserved #}
|
||||
{% load origin %}
|
||||
|
||||
{% block title %}NomCom deleted{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% origin %}
|
||||
<h1>NomCom deleted</h1>
|
||||
|
||||
<p class="alert alert-success">All data about the NomCom has been removed.</p>
|
||||
|
||||
{% endblock %}
|
|
@ -10,8 +10,6 @@
|
|||
{% origin %}
|
||||
<h2>Edit members</h2>
|
||||
|
||||
{% bootstrap_messages %}
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
|
|
|
@ -14,12 +14,6 @@
|
|||
{% origin %}
|
||||
<h2>Settings</h2>
|
||||
|
||||
{% if message %}
|
||||
<div class="alert alert-{{ message.0 }}">{{ message.1 }}</div>
|
||||
{% endif %}
|
||||
|
||||
{% bootstrap_messages %}
|
||||
|
||||
<form enctype="multipart/form-data" method="post">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
|
@ -37,12 +31,6 @@
|
|||
{% endbuttons %}
|
||||
</form>
|
||||
|
||||
<h2>Delete Nomcom</h2>
|
||||
|
||||
<p>
|
||||
<a class="btn btn-danger" href="{% url "nomcom_delete_nomcom" year %}" class="deletelink">Delete NomCom</a>
|
||||
</p>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
|
|
|
@ -8,14 +8,9 @@
|
|||
|
||||
{% block nomcom_content %}
|
||||
{% origin %}
|
||||
{% bootstrap_messages %}
|
||||
|
||||
<h2>Edit email<br><small>{{ nominee }}</small></h2>
|
||||
|
||||
{% if message %}
|
||||
<p class="alert alert-{{ message.0 }}">{{ message.1 }}</p>
|
||||
{% endif %}
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
|
|
|
@ -15,8 +15,6 @@
|
|||
{% origin %}
|
||||
<h2>{% if position %}Edit{% else %}Add{% endif %} position</h2>
|
||||
|
||||
{% bootstrap_messages %}
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
{% endif %}
|
||||
</dl>
|
||||
|
||||
<form method="post">
|
||||
<form id="templateform" method="post">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
|
||||
|
|
|
@ -5,24 +5,30 @@
|
|||
{% load bootstrap3 %}
|
||||
{% load nomcom_tags %}
|
||||
|
||||
{% block morecss %}
|
||||
.btn-group-vertical .btn {
|
||||
text-align: left;
|
||||
}
|
||||
.btn-group-vertical .btn .badge {
|
||||
float:right; margin-top: -1.3em;
|
||||
}
|
||||
{% endblock %}
|
||||
|
||||
{% block subtitle %} - Feedback{% endblock %}
|
||||
|
||||
{% block nomcom_content %}
|
||||
{% origin %}
|
||||
<p class="alert alert-info">
|
||||
First select a nominee from the list of nominees to provide input about that nominee.
|
||||
This will fill in the non-editable fields in the form.
|
||||
<p id="instructions" class="alert alert-info">
|
||||
{% if nomcom.group.state_id == 'conclude' %}
|
||||
Feedback to this nomcom is closed.
|
||||
{% else %}
|
||||
Select a nominee from the list of nominees to the right to obtain a new feedback form.
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
{% if message %}
|
||||
<p class="alert alert-{{ message.0 }}">{{ message.1 }}</p>
|
||||
{% endif %}
|
||||
|
||||
{% bootstrap_messages %}
|
||||
|
||||
{% if nomcom|has_publickey %}
|
||||
<div class="row">
|
||||
<div class="col-sm-4 col-sm-push-8">
|
||||
<div id="nominees" class="col-sm-4 col-sm-push-8">
|
||||
<h3>Nominees</h3>
|
||||
|
||||
{% for p in positions %}
|
||||
|
@ -30,9 +36,14 @@
|
|||
<h4>{{ p.name }}</h4>
|
||||
<div class="btn-group-vertical form-group">
|
||||
{% for np in p.nomineeposition_set.accepted.not_duplicated %}
|
||||
<a class="btn btn-default btn-xs" href="?nominee={{np.nominee.id}}&position={{ np.position.id}}">
|
||||
{{ np.nominee }}
|
||||
{% add_num_nominations user np.position np.nominee %}
|
||||
<a class="btn btn-default btn-xs" {% if nomcom.group.state_id != 'conclude' %}href="?nominee={{np.nominee.id}}&position={{ np.position.id}}"{% endif %}>
|
||||
{{ np.nominee.name }}
|
||||
{% with count=counts|lookup:np.position.id|lookup:np.nominee.id %}
|
||||
<span class="badge"
|
||||
title="{% if count %}{{count}} earlier comment{{count|pluralize}} from you {% else %}You have not yet provided feedback {% endif %} on {{np.nominee.email.address}} as {{np.position}}">
|
||||
{{ count | default:"no feedback" }}
|
||||
</span>
|
||||
{% endwith %}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
@ -42,22 +53,30 @@
|
|||
<p>
|
||||
An number after a name indicates
|
||||
that you have given comments on this nominee
|
||||
earlier. If you position the mouse pointer over
|
||||
it, you should see how many comments
|
||||
exist from you for this nominee.
|
||||
earlier. Position the mouse pointer over
|
||||
the badge, for more information about this
|
||||
nominee.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-8 col-sm-pull-4">
|
||||
<h3>Provide feedback</h3>
|
||||
{% if form %}
|
||||
<h3>Provide feedback
|
||||
{% if form.position %}
|
||||
about {{form.nominee.email.person.name}} ({{form.nominee.email.address}}) for the {{form.position.name}} position.</p>
|
||||
{% endif %}
|
||||
</h3>
|
||||
<p>This feedback will only be available to <a href="{% url 'nomcom_year_index' year=year %}">NomCom {{year}}</a>.
|
||||
You may have the feedback mailed back to you by selecting the option below.</p>
|
||||
|
||||
<form id="feedbackform" method="post">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
{% buttons %}
|
||||
<input class="btn btn-primary" type="submit" value="Save" name="save" {% if submit_disabled %}disabled="disabled"{% endif %}>
|
||||
{% endbuttons %}
|
||||
<form id="feedbackform" method="post">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
{% buttons %}
|
||||
<input class="btn btn-primary" type="submit" value="Save" name="save">
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
|
|
@ -8,31 +8,38 @@
|
|||
{% origin %}
|
||||
<h2>Positions in {{ nomcom.group }}</h2>
|
||||
|
||||
<a class="btn btn-default" href="{% url "nomcom_add_position" year %}">Add new position</a>
|
||||
{% 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>Incumbent</dt>
|
||||
<dd>{% if position.incumbent %}{{ position.incumbent.person }} <{{ position.incumbent.address }}>{% else %}None{% endif %}</dd>
|
||||
<dt>Is open</dt>
|
||||
<dd>{{ position.is_open }}</dd>
|
||||
<dt>Templates</dt>
|
||||
<dd>
|
||||
{% for template in position.get_templates %}
|
||||
<a href="{% url "nomcom_edit_template" year template.id %}">{{ template }}</a><br>
|
||||
{% endfor %}
|
||||
</dd>
|
||||
{% if nomcom.group.state_id == 'active' %}
|
||||
<dt>Actions</dt>
|
||||
<dd>
|
||||
<a class="btn btn-default" href="{% url "nomcom_edit_position" year position.id %}">Edit</a>
|
||||
<a class="btn btn-default" href="{% url "nomcom_remove_position" year position.id %}">Remove</a>
|
||||
</dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<p>There are no positions defined.</p>
|
||||
{% endif %}
|
||||
|
|
11
ietf/templates/nomcom/merge_request.txt
Normal file
11
ietf/templates/nomcom/merge_request.txt
Normal file
|
@ -0,0 +1,11 @@
|
|||
The following Person records have been identified by the NomCom chair as potential duplicates.
|
||||
|
||||
The following records:{% for p in duplicate_persons %}
|
||||
{{p.name}} ({{p.id}}) [{{p.email_set.all|join:", "}}]{% endfor %}
|
||||
|
||||
appear to be duplicates of this person (which should be kept)
|
||||
{{primary_person.name}} ({{primary_person.id}}) [{{primary_person.email_set.all|join:", "}}]
|
||||
|
||||
Please verify that these are indeed duplicates, and if so, merge them.
|
||||
|
||||
Thanks in advance.
|
|
@ -9,7 +9,7 @@
|
|||
{% block content %}
|
||||
{% origin %}
|
||||
|
||||
<h1>NomCom {{ year }} <small>Private area</small></h1>
|
||||
<h1>NomCom {{ year }} {% if nomcom.group.state_id == 'conclude' %}(Concluded){% endif %} <small>Private area {% if is_chair_task %}- Chair/Advisors only{% endif %}</small></h1>
|
||||
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li {% if selected == "index" %}class="active"{% endif %}><a href="{% url "nomcom_private_index" year %}">Nominees</a></li>
|
||||
|
@ -17,31 +17,32 @@
|
|||
{% if nomcom|has_publickey %}
|
||||
<li {% if selected == "nominate" %}class="active"{% endif %}><a href="{% url "nomcom_private_nominate" year %}">Nominate</a></li>
|
||||
<li {% if selected == "feedback" %}class="active"{% endif %}><a href="{% url "nomcom_private_feedback" year %}">Enter feedback</a></li>
|
||||
<li {% if selected == "questionnaire" %}class="active"{% endif %}><a href="{% url "nomcom_private_questionnaire" year %}">Questionnaire response</a></li>
|
||||
{% endif %}
|
||||
|
||||
<li {% if selected == "view_feedback" %}class="active"{% endif %}><a href="{% url "nomcom_view_feedback" year %}">View feedback</a></li>
|
||||
<li {% if selected == "private_key" %}class="active"{% endif %}><a href="{% url "nomcom_private_key" year %}">Private key</a></li>
|
||||
|
||||
{% if user|is_chair:year %}
|
||||
{% if user|is_chair_or_advisor:year %}
|
||||
<li class="dropdown">
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#">Chair <span class="caret"></span></a>
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#">Chair/Advisor Tasks <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li {% if selected == "feedback_pending" %}class="active"{% endif %}><a href="{% url "nomcom_view_feedback_pending" year %}">Pending feedback</a></li>
|
||||
<li {% if selected == "feedback_email" %}class="active"{% endif %}><a href="{% url "nomcom_private_feedback_email" year %}">Enter email feedback</a></li>
|
||||
<li {% if selected == "send_accept_reminder" %}class="active"{% endif %}><a href="{% url "nomcom_send_reminder_mail" year "accept" %}">Send accept reminder</a></li>
|
||||
<li {% if selected == "send_questionnaire_reminder" %}class="active"{% endif %}><a href="{% url "nomcom_send_reminder_mail" year "questionnaire" %}">Send questionnaire reminder</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li class="dropdown">
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#">Edit <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li {% if selected == "edit_nomcom" %}class="active"{% endif %}><a href="{% url "nomcom_edit_nomcom" year %}">Edit Settings</a></li>
|
||||
<li {% if selected == "edit_templates" %}class="active"{% endif %}><a href="{% url "nomcom_list_templates" year %}">Edit Pages</a></li>
|
||||
<li {% if selected == "edit_positions" %}class="active"{% endif %}><a href="{% url "nomcom_list_positions" year %}">Edit Positions</a></li>
|
||||
<li {% if selected == "merge" %}class="active"{% endif %}><a href="{% url "nomcom_private_merge" year %}">Merge Email Addresses</a></li>
|
||||
<li {% if selected == "edit_members" %}class="active"{% endif %}><a href="{% url "nomcom_edit_members" year %}">Edit Nomcom Members</a></li>
|
||||
{% if nomcom.group.state_id == 'active' %}
|
||||
<li role = "presentation" class = "dropdown-header">Feedback Management</li>
|
||||
<li {% if selected == "feedback_pending" %}class="active"{% endif %}><a href="{% url "nomcom_view_feedback_pending" year %}">Classify pending feedback</a></li>
|
||||
<li {% if selected == "feedback_email" %}class="active"{% endif %}><a href="{% url "nomcom_private_feedback_email" year %}">Enter email feedback</a></li>
|
||||
<li {% if selected == "questionnaire" %}class="active"{% endif %}><a href="{% url "nomcom_private_questionnaire" year %}">Enter questionnaire response</a></li>
|
||||
<li {% if selected == "send_accept_reminder" %}class="active"{% endif %}><a href="{% url "nomcom_send_reminder_mail" year "accept" %}">Send accept reminder</a></li>
|
||||
<li {% if selected == "send_questionnaire_reminder" %}class="active"{% endif %}><a href="{% url "nomcom_send_reminder_mail" year "questionnaire" %}">Send questionnaire reminder</a></li>
|
||||
<li {% if selected == "merge" %}class="active"{% endif %}><a href="{% url "nomcom_private_merge" year %}">Request Nominee Merge</a></li>
|
||||
{% endif %}
|
||||
<li role = "presentation" class = "dropdown-header">Nomcom Configuration</li>
|
||||
<li {% if selected == "edit_nomcom" %}class="active"{% endif %}><a href="{% url "nomcom_edit_nomcom" year %}">Edit Settings</a></li>
|
||||
<li {% if selected == "edit_templates" %}class="active"{% endif %}><a href="{% url "nomcom_list_templates" year %}">Edit Pages</a></li>
|
||||
<li {% if selected == "edit_positions" %}class="active"{% endif %}><a href="{% url "nomcom_list_positions" year %}">Edit Positions</a></li>
|
||||
{% 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>
|
||||
{% endif %}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
{% block content %}
|
||||
{% origin %}
|
||||
|
||||
<h1>NomCom {{ year }}</h1>
|
||||
<h1>NomCom {{ year }} {% if nomcom.group.state_id == 'conclude' %}(Concluded){% endif %}</h1>
|
||||
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li {% if selected == "index" %}class="active"{% endif %}><a href="{% url "nomcom_year_index" year %}">Home</a></li>
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
{# Copyright The IETF Trust 2015, All Rights Reserved #}{% load origin %}{% origin %}
|
||||
<div class="baseform">
|
||||
{% for fieldset in form.get_fieldsets %}
|
||||
{% if fieldset.name %}
|
||||
<div class="fieldset">
|
||||
<h2>{{ fieldset.name }}</h2>
|
||||
{% endif %}
|
||||
|
||||
{% for field in fieldset.fields %}
|
||||
<div id="baseform-fieldname-{{ field.html_name }}"
|
||||
{% if field.field.widget.is_hidden %}style="display: none;"{% endif %}
|
||||
class="{% if field.errors %}fieldError {% endif %}field BaseFormStringWidget{% if field.field.column_style %} {{ field.field.column_style }}{% endif %}">
|
||||
<label for="id_{{ field.html_name }}">{{ field.label }}
|
||||
{% if field.field.required %}
|
||||
<span class="fieldRequired" title="Required">*</span>
|
||||
{% endif %}
|
||||
</label>
|
||||
<div class="fieldWidget">
|
||||
<div id="{{ field.html_name }}_help" class="formHelp"> {{ field.help_text }}</div>
|
||||
{{ field }}
|
||||
<pre>{{ field.errors }}</pre>
|
||||
</div>
|
||||
<div class="endfield"></div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% if fieldset.name %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
|
|
@ -8,14 +8,9 @@
|
|||
|
||||
{% block nomcom_content %}
|
||||
{% origin %}
|
||||
{% if message %}
|
||||
<div class="alert alert-{{ message.0 }}">{{ message.1 }}</div>
|
||||
{% endif %}
|
||||
|
||||
{% if nomcom|has_publickey %}
|
||||
{% bootstrap_messages %}
|
||||
|
||||
<form id="questionnnaireform" method="post">
|
||||
{% if form %}
|
||||
<form id="paste-email-feedback-form" method="post">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
{% buttons %}
|
||||
|
|
|
@ -66,14 +66,14 @@
|
|||
</form>
|
||||
|
||||
|
||||
{% if is_chair %}
|
||||
{% if is_chair and nomcom.group.state_id == 'active' %}
|
||||
<form class="form-inline" id="batch-action-form" method="post">{% csrf_token %}
|
||||
{% endif %}
|
||||
|
||||
<table class="table table-condensed table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
{% if is_chair %}<th colspan="2"><span class="fa fa-check"></span></th>{% endif %}
|
||||
{% if is_chair and nomcom.group.state_id == 'active' %}<th colspan="2"><span class="fa fa-check"></span></th>{% endif %}
|
||||
<th>Nominee</th>
|
||||
<th>Position</th>
|
||||
<th>State</th>
|
||||
|
@ -83,7 +83,7 @@
|
|||
<tbody>
|
||||
{% for np in nominee_positions %}
|
||||
<tr>
|
||||
{% if is_chair %}
|
||||
{% if is_chair and nomcom.group.state_id == 'active' %}
|
||||
<td><input class="batch-select" type="checkbox" value="{{ np.id }}" name="selected"></td>
|
||||
<td class="edit"><a class="btn btn-default btn-xs" href="{% url "nomcom_edit_nominee" year np.nominee.id %}">Edit</a></td>
|
||||
{% endif %}
|
||||
|
@ -101,23 +101,21 @@
|
|||
|
||||
{% if is_chair %}
|
||||
|
||||
{% if message %}
|
||||
<p class="alert alert-{{ message.0 }}">{{ message.1 }}</p>
|
||||
{% if nomcom.group.state_id == 'active' %}
|
||||
<div class="form-group">
|
||||
<label>Action:</label>
|
||||
<select class="form-control" name="action">
|
||||
<option value="" selected="selected">---------</option>
|
||||
<option value="set_as_accepted">Set as accepted</option>
|
||||
<option value="set_as_pending">Set as pending</option>
|
||||
<option value="set_as_declined">Set as declined</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-warning" type="submit" title="Run action">Apply</button>
|
||||
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
<div class="form-group">
|
||||
<label>Action:</label>
|
||||
<select class="form-control" name="action">
|
||||
<option value="" selected="selected">---------</option>
|
||||
<option value="set_as_accepted">Set as accepted</option>
|
||||
<option value="set_as_pending">Set as pending</option>
|
||||
<option value="set_as_declined">Set as declined</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-warning" type="submit" title="Run action">Apply</button>
|
||||
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -10,10 +10,6 @@
|
|||
{% origin %}
|
||||
<h2>Enter private key</h2>
|
||||
|
||||
{% if message %}
|
||||
<p class="alert alert-{{ message.0 }}">{{ message.1 }}</p>
|
||||
{% endif %}
|
||||
|
||||
<p>In order to access the {{ nomcom.group }} data you have to enter your private key. Please paste it in the text area below. The key must be in the following format:</p>
|
||||
|
||||
<pre>
|
||||
|
|
|
@ -1,44 +1,53 @@
|
|||
{% extends "nomcom/nomcom_private_base.html" %}
|
||||
{# Copyright The IETF Trust 2015, All Rights Reserved #}
|
||||
{% load origin %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% load bootstrap3 %}
|
||||
|
||||
{% block subtitle %} - Merging emails {% endblock %}
|
||||
{% block pagehead %}
|
||||
<link rel="stylesheet" href="{% static 'select2/select2.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'select2-bootstrap-css/select2-bootstrap.min.css' %}">
|
||||
{% endblock %}
|
||||
|
||||
{% block subtitle %} - Request Nominee Merge {% endblock %}
|
||||
|
||||
{% block nomcom_content %}
|
||||
{% origin %}
|
||||
<h2>Merging nominee email addresses</h2>
|
||||
<h2>Request Nominee Merge</h2>
|
||||
|
||||
<p>
|
||||
If a nominee has been nominated with multiple email addresses, the nominee will
|
||||
appear multiple times in the nomination list, as the email address is used as
|
||||
the unique identifier for each nominee. In order to permit comments and nominations
|
||||
to be submitted under multiple email addresses, there is a list of secondary email
|
||||
addresses which needs to be kept up-to-date. When nominations of one particular nominee
|
||||
have already been made under different email addresses, the nomination comments from the
|
||||
secondary address also needs to be merged with those under the primary address.
|
||||
The nomination system encourages the community to nominate people by selecting
|
||||
their email address from the set of addresses the tracker already knows. In order
|
||||
to allow a person who does not yet have a datatracker account to be nominated, the
|
||||
system also provides a way for the community to nominate people with a new,
|
||||
previously unknown email address. When this option is chosen, a new Person record
|
||||
is created and associated with the new address.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
It doesn't matter particularly which email address is used as primary, as far as the
|
||||
nominee information maintenance goes, but it's probably handier for the nomcom if the
|
||||
primary address is the one which the nominee prefers at the time.
|
||||
Occasionally, this new address should have been associated with an existing person
|
||||
instead. This will happen particularly if the community member uses a slightly incorrect
|
||||
address (such as a typo), or knows the person they want to nominate by a very old or very
|
||||
new address that is not yet in the tracker. When this happens, you can use this form to
|
||||
ask the secretariat to merge the two Person records. The secretariat has a process
|
||||
for verifying that the addresses both belong to the same person, and a tool that
|
||||
can correct the relevant data.
|
||||
</p>
|
||||
|
||||
{% if message %}
|
||||
<div class="alert alert-{{ message.0 }}">{{ message.1 }}</div>
|
||||
{% if form %}
|
||||
<form id="mergeform" method="post">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
|
||||
{% buttons %}
|
||||
<input class="btn btn-primary" type="submit" value="Save" name="save">
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
{% bootstrap_messages %}
|
||||
|
||||
<form id="nominateform" method="post">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
|
||||
{% buttons %}
|
||||
<input class="btn btn-primary" type="submit" value="Save" name="save">
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script src="{% static 'select2/select2.min.js' %}"></script>
|
||||
<script src="{% static 'ietf/js/select2-field.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
{% extends "nomcom/nomcom_private_base.html" %}
|
||||
{# Copyright The IETF Trust 2015, All Rights Reserved #}
|
||||
{% load origin %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% load bootstrap3 %}
|
||||
{% load nomcom_tags %}
|
||||
|
||||
{% block pagehead %}
|
||||
<link rel="stylesheet" href="{% static 'select2/select2.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'select2-bootstrap-css/select2-bootstrap.min.css' %}">
|
||||
{% endblock %}
|
||||
|
||||
{% block subtitle %} - Nominate{% endblock %}
|
||||
|
||||
{% block nomcom_content %}
|
||||
{% origin %}
|
||||
<h2>Candidate nomination</h2>
|
||||
|
||||
{% bootstrap_messages %}
|
||||
|
||||
{% if message %}
|
||||
<p class="alert alert-{{ message.0 }}">{{ message.1 }}</p>
|
||||
{% endif %}
|
||||
|
||||
{% if nomcom|has_publickey %}
|
||||
{% if form %}
|
||||
|
||||
<form id="nominate-form" method="post">
|
||||
{% csrf_token %}
|
||||
|
@ -30,3 +30,8 @@
|
|||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script src="{% static 'select2/select2.min.js' %}"></script>
|
||||
<script src="{% static 'ietf/js/select2-field.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -9,18 +9,12 @@
|
|||
|
||||
{% block nomcom_content %}
|
||||
|
||||
{% if message %}
|
||||
<p class="alert alert-{{ message.0 }}">{{ message.1 }}</p>
|
||||
{% endif %}
|
||||
|
||||
{% if nomcom|has_publickey %}
|
||||
{% if form %}
|
||||
{% if questionnaire_response %}
|
||||
<h2>Questionnaire response</h2>
|
||||
{{ questionnaire_response }}
|
||||
{% endif %}
|
||||
|
||||
{% bootstrap_messages %}
|
||||
|
||||
<form id="questionnnaireform" method="post">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
|
|
|
@ -8,14 +8,13 @@
|
|||
|
||||
{% block nomcom_content %}
|
||||
{% origin %}
|
||||
{% if message %}
|
||||
<div class="alert alert-{{ message.0 }}">{{ message.1 }}</div>
|
||||
{% endif %}
|
||||
|
||||
{% if need_confirmation %}
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
{% bootstrap_form form %}
|
||||
|
||||
{% buttons %}
|
||||
<button class="btn btn-primary" type="submit">Save</button>
|
||||
{% endbuttons %}
|
||||
|
|
|
@ -1,21 +1,22 @@
|
|||
{% extends "nomcom/nomcom_public_base.html" %}
|
||||
{# Copyright The IETF Trust 2015, All Rights Reserved #}
|
||||
{% load origin %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% load bootstrap3 %}
|
||||
{% load nomcom_tags %}
|
||||
|
||||
{% block pagehead %}
|
||||
<link rel="stylesheet" href="{% static 'select2/select2.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'select2-bootstrap-css/select2-bootstrap.min.css' %}">
|
||||
{% endblock %}
|
||||
|
||||
{% block subtitle %} - Nominate{% endblock %}
|
||||
|
||||
{% block nomcom_content %}
|
||||
{% origin %}
|
||||
{% if message %}
|
||||
<p class="alert alert-{{ message.0 }}">{{ message.1 }}</p>
|
||||
{% endif %}
|
||||
|
||||
{% bootstrap_messages %}
|
||||
|
||||
{% if nomcom|has_publickey %}
|
||||
{% if form %}
|
||||
<form id="nominate-form" method="post">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
|
@ -26,3 +27,8 @@
|
|||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script src="{% static 'select2/select2.min.js' %}"></script>
|
||||
<script src="{% static 'ietf/js/select2-field.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -7,16 +7,24 @@
|
|||
{% block nomcom_content %}
|
||||
{% origin %}
|
||||
<h2>Position: {{ position }}</h2>
|
||||
<dl>
|
||||
<dt>Description:</dt>
|
||||
<dd>{{ position.description }}</dd>
|
||||
<dt>Incumbent:</dt>
|
||||
<dd>{{ position.incumbent }}</dd>
|
||||
<dt>Is open:</dt>
|
||||
<dd>{{ position.is_open }}</dd>
|
||||
</dl>
|
||||
|
||||
<h3>Do you want to remove it?</h3>
|
||||
<p>This position is currently {{position.is_open|yesno:"open,closed"}}.</p>
|
||||
<p>It has {{position.feedback_set.count|default:"no"}} feedback objects associated with it.</p>
|
||||
{% if position.feedback_set.count %}
|
||||
<p>
|
||||
<span class="alert alert-warning">Unless this is a position created only for testing, deleting it is likely to be harmful. All of the feedback will also be deleted.</span>
|
||||
</p>
|
||||
<p>
|
||||
{% if position.is_open %}
|
||||
If you are just wanting the position to disappear from the lists available to the community for providing nominations and feedback, instead of deleting the position, edit the position and change is_open to False.
|
||||
{% else %}
|
||||
Since the position is closed, it will not appear on the lists available to the community for providing nominations and feedback.
|
||||
{% endif %}
|
||||
</p>
|
||||
<p>If this is just a test position, it is ok to delete it.</p>
|
||||
{% else %}
|
||||
<p>This position is safe to delete.</p>
|
||||
{% endif %}
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
|
@ -24,8 +32,8 @@
|
|||
<input type="hidden" name="remove" value="1">
|
||||
|
||||
{% buttons %}
|
||||
<a class="btn btn-default pull-right" href="../">No, get me out of here</a>
|
||||
<button class="btn btn-primary" type="submit">Yes, remove it</button>
|
||||
<button class="btn btn-primary btn-warning" type="submit">Delete</button>
|
||||
<a class="btn btn-default" href="{% url 'nomcom_list_positions' year %}">Cancel</a>
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
|
||||
|
|
|
@ -26,8 +26,7 @@
|
|||
<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}}
|
||||
{{ position.get_requirement|safe }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
|
|
@ -11,51 +11,48 @@
|
|||
{% origin %}
|
||||
<h2>Send remider to {{reminder_description}}</h2>
|
||||
|
||||
<p>The message that will be sent is as follows:</p>
|
||||
<pre>{{ mail_template.content|wrap_text:80 }}</pre>
|
||||
{% if nomcom.group.state_id == 'active' %}
|
||||
<p>The message that will be sent is as follows:</p>
|
||||
<pre>{{ mail_template.content|wrap_text:80 }}</pre>
|
||||
|
||||
{% if mail_template %}
|
||||
<p>
|
||||
<a class="btn btn-default" href="{% url "nomcom_edit_template" year mail_template.id %}">Edit the message</a>
|
||||
</p>
|
||||
{% if mail_template %}
|
||||
<p>
|
||||
<a class="btn btn-default" href="{% url "nomcom_edit_template" year mail_template.id %}">Edit the message</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<p>These are the nominees that are in the '{{state_description}}' state for the listed positions. </p>
|
||||
|
||||
<p>The message that will be sent is shown below the list of nominees. </p>
|
||||
{% endif %}
|
||||
|
||||
<p>These are the nominees that are in the '{{state_description}}' state for the listed positions. </p>
|
||||
|
||||
<p>The message that will be sent is shown below the list of nominees. </p>
|
||||
|
||||
{% bootstrap_messages %}
|
||||
|
||||
{% if message %}
|
||||
<div class="alert alert-{{ message.0 }}">{{ message.1 }}</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<table class="table table-condensed table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><span class="fa fa-check"></span></th>
|
||||
<th>Nominee</th>
|
||||
<th>Positions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for nominee in nominees %}
|
||||
{% if nomcom.group.state_id == 'active' %}
|
||||
<form id="reminderform " method="post">
|
||||
{% csrf_token %}
|
||||
<table class="table table-condensed table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>
|
||||
<input class="batch-select" type="checkbox" value="{{ nominee.id }}" name="selected" checked="checked">
|
||||
</td>
|
||||
<td>{{ nominee }}</td>
|
||||
<td>{{nominee.interesting_positions|join:", "}}</td>
|
||||
<th><span class="fa fa-check"></span></th>
|
||||
<th>Nominee</th>
|
||||
<th>Positions</th>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% buttons %}
|
||||
<input class="btn btn-primary" type="submit" name="submit" value="Submit request">
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for nominee in nominees %}
|
||||
<tr>
|
||||
<td>
|
||||
<input class="batch-select" type="checkbox" value="{{ nominee.id }}" name="selected" checked="checked">
|
||||
</td>
|
||||
<td>{{ nominee }}</td>
|
||||
<td>{{nominee.interesting_positions|join:", "}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% buttons %}
|
||||
<input class="btn btn-primary" type="submit" name="submit" value="Submit request">
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
41
ietf/templates/nomcom/show_template.html
Normal file
41
ietf/templates/nomcom/show_template.html
Normal file
|
@ -0,0 +1,41 @@
|
|||
{% extends "nomcom/nomcom_private_base.html" %}
|
||||
{# Copyright The IETF Trust 2015, All Rights Reserved #}
|
||||
{% load origin %}
|
||||
|
||||
{% block subtitle %} - Template: {{ template }}{% endblock %}
|
||||
|
||||
{% load bootstrap3 %}
|
||||
|
||||
{% block nomcom_content %}
|
||||
{% origin %}
|
||||
<h2>Template: {{ template }}</h2>
|
||||
|
||||
<dl>
|
||||
<dt>Title</dt>
|
||||
<dd>{{ template.title }}</dt>
|
||||
<dt>Group</dt>
|
||||
<dd>{{ template.group }}</dd>
|
||||
<dt>Template type</dt>
|
||||
<dd>{{ template.type.name }}:
|
||||
{% if template.type.slug == "rst" %}
|
||||
This template uses the syntax of reStructuredText. Get a quick reference at <a href="http://docutils.sourceforge.net/docs/user/rst/quickref.html">http://docutils.sourceforge.net/docs/user/rst/quickref.html</a>. You can do variable interpolation with $variable if the template allows any variable.
|
||||
{% elif template.type.slug == "django" %}
|
||||
This template uses the syntax of the default django template framework. Get more info at <a href="https://docs.djangoproject.com/en/dev/topics/templates/">https://docs.djangoproject.com/en/dev/topics/templates/</a>. You can do variable interpolation with the current django markup {{variable}} if the template allows any variable.
|
||||
{% elif template.type.slug == "plain" %}
|
||||
This template uses plain text, so no markup is used. You can do variable interpolation with $variable if the template allows any variable.
|
||||
{% endif %}
|
||||
</dd>
|
||||
{% if template.variables %}
|
||||
<dt>Variables allowed in this template</dt>
|
||||
<dd>{{ template.variables|linebreaks }}</dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
|
||||
<div class = "panel panel-default">
|
||||
<p class='pasted'>{{ template.content }}</p>
|
||||
</div>
|
||||
|
||||
{% buttons %}
|
||||
<a class="btn btn-default pull-right" href="{% if return_url %}{{ return_url }}{% else %}../{% endif %}">Back</a>
|
||||
{% endbuttons %}
|
||||
{% endblock nomcom_content %}
|
|
@ -10,28 +10,48 @@
|
|||
{% origin %}
|
||||
<h2>Feedback related to nominees</h2>
|
||||
|
||||
<table class="table table-condensed table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nominee</th>
|
||||
{% for ft in feedback_types %}
|
||||
<th>{{ ft.name }}</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for nominee, feedback in nominees_feedback.items %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{% url "nomcom_view_feedback_nominee" year nominee.id %}#comment">{{ nominee }}
|
||||
</td>
|
||||
{% for f in feedback %}
|
||||
<td>{{ f.1 }}</td>
|
||||
{% regroup nominees_feedback by nominee.staterank as stateranked_nominees %}
|
||||
{% for staterank in stateranked_nominees %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
{% if staterank.grouper == 0 %}
|
||||
Accepted nomination for at least one position
|
||||
{% elif staterank.grouper == 1 %}
|
||||
Pending for at least one position and has not accepted any nomination
|
||||
{% else %}
|
||||
Declined each nominated position
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table table-condensed table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="col-sm-9">Nominee</th>
|
||||
{% for ft in feedback_types %}
|
||||
<th class="col-sm-1 text-center">{{ ft.name }}</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for fb_dict in staterank.list %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{% url "nomcom_view_feedback_nominee" year fb_dict.nominee.id %}">{{ fb_dict.nominee.name }}</a>
|
||||
<span class="hidden-xs"><{{fb_dict.nominee.email.address}}></span>
|
||||
</td>
|
||||
{% for fbtype_name, fbtype_count, fbtype_newflag in fb_dict.feedback %}
|
||||
<td class="text-right">
|
||||
{% if fbtype_newflag %}<span class="label label-success">New</span>{% endif %}
|
||||
{{ fbtype_count }}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% if independent_feedback_types %}
|
||||
<h2>Feedback not related to Nominees</h2>
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
{% if feedback.type.slug == ft.slug %}
|
||||
{% if forloop.first %}<p></p>{% else %}<hr>{% endif %}
|
||||
<dl class="dl-horizontal">
|
||||
<dt>From</dt>
|
||||
<dt>{% if feedback.time > last_seen_time %}<span class="label label-success">New</span>{% endif %}From</dt>
|
||||
<dd>{{ feedback.author|formatted_email|default:"Anonymous" }}
|
||||
{% if ft.slug == "nomina" and feedback.nomination_set.first.share_nominator %}
|
||||
<span class="bg-info"> OK to share name with nominee</span>
|
||||
|
|
|
@ -3,17 +3,24 @@
|
|||
{% load origin %}
|
||||
|
||||
{% load bootstrap3 %}
|
||||
{% load staticfiles %}
|
||||
{% load nomcom_tags %}
|
||||
|
||||
{% block pagehead %}
|
||||
<link rel="stylesheet" href="{% static 'select2/select2.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'select2-bootstrap-css/select2-bootstrap.min.css' %}">
|
||||
{% endblock %}
|
||||
|
||||
{% block subtitle %} - Feeback pending{% endblock %}
|
||||
|
||||
{% block morecss %}
|
||||
.nominee_multi_select { resize: vertical; }
|
||||
{% endblock %}
|
||||
|
||||
{% block nomcom_content %}
|
||||
{% origin %}
|
||||
<h2>Feedback pending from email list</h2>
|
||||
|
||||
{% if message %}
|
||||
<p class="alert alert-{{ message.0 }}">{{ message.1 }}</p>
|
||||
{% endif %}
|
||||
|
||||
{% if formset.forms %}
|
||||
<form method="post">
|
||||
|
@ -145,9 +152,6 @@
|
|||
|
||||
{% buttons %}
|
||||
<input class="btn btn-primary" type="submit" value="Classify">
|
||||
{% if default_type %}
|
||||
<input class="btn btn-default" type="submit" name="move_to_default" value="Move all unclassified feedback to {{ default_type }}">
|
||||
{% endif %}
|
||||
{% endbuttons %}
|
||||
{% endif %}
|
||||
</form>
|
||||
|
@ -157,3 +161,8 @@
|
|||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script src="{% static 'select2/select2.min.js' %}"></script>
|
||||
<script src="{% static 'ietf/js/select2-field.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -25,23 +25,7 @@
|
|||
<dt>From</dt>
|
||||
<dd>{{ feedback.author|formatted_email|default:"Anonymous" }}</dd>
|
||||
<dt>Date</dt>
|
||||
<dd>{{ feedback.time|date:"Y-m-d" }})</dd>
|
||||
|
||||
{% if ft.slug == "nomina" %}
|
||||
{% for fn in feedback.nomination_set.all %}
|
||||
{% if fn.candidate_name %}
|
||||
<dt>Nominee</dt>
|
||||
<dd>{{ fn.candidate_name }}</dd>
|
||||
{% endif %}
|
||||
{% if fn.candidate_phone %}
|
||||
<dt>Nominee phone</dt>
|
||||
<dd>{{ fn.candidate_phone }}</dd>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<dt>Positions</dt>
|
||||
<dd>{{ feedback.positions.all|join:"," }}</dd>
|
||||
<dd>{{ feedback.time|date:"Y-m-d" }}</dd>
|
||||
<dt>Body</dt>
|
||||
<dd class="pasted">{% decrypt feedback.comments request year 1 %}</dd>
|
||||
</dl>
|
||||
|
|
|
@ -25,3 +25,5 @@ six>=1.8.0
|
|||
wsgiref>=0.1.2
|
||||
xml2rfc>=2.5.0
|
||||
django>=1.7.10,<1.8
|
||||
factory-boy>=2.6.0
|
||||
Unidecode>=0.4.18
|
||||
|
|
Loading…
Reference in a new issue