Skip to content

Commit c8bbfba

Browse files
committed
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 ietf-tools#1845. Organize the list of nominees on the feedback page. Fixes ietf-tools#1786 and ietf-tools#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 ietf-tools#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 ietf-tools#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 ietf-tools#1788 and ietf-tools#1795. Regroup multiselect options to make classifying pending feedback simpler. Make the control larger and resizable. Fixes ietf-tools#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 ietf-tools#1847. Added merging nominees to the secretariat's persson merging script. Show information for concluded nomcoms. Close feedback and nomination for concluded nomcoms. Fixes ietf-tools#1856. Improve the questionnaire templates, reminding the nominee that receiving the questionnaire does not imply they have accepted a nomination. Fixes ietf-tools#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 ietf-tools#1867 and ietf-tools#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 ietf-tools#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
2 parents 95e2147 + 17f7b63 commit c8bbfba

67 files changed

Lines changed: 2644 additions & 1139 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

ietf/bin/merge-person-records

Lines changed: 3 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,10 @@ import django
1212
django.setup()
1313

1414
import argparse
15-
import pprint
16-
from django.contrib import admin
17-
from django.contrib.auth.models import User
1815
from ietf.person.models import Person
1916

17+
from ietf.person.utils import merge_persons
18+
2019
parser = argparse.ArgumentParser()
2120
parser.add_argument("source_id",type=int)
2221
parser.add_argument("target_id",type=int)
@@ -30,62 +29,4 @@ response = raw_input('Ok to continue y/n? ')
3029
if response.lower() != 'y':
3130
sys.exit()
3231

33-
# merge emails
34-
for email in source.email_set.all():
35-
print "Merging email: {}".format(email.address)
36-
email.person = target
37-
email.save()
38-
39-
# merge aliases
40-
target_aliases = [ a.name for a in target.alias_set.all() ]
41-
for alias in source.alias_set.all():
42-
if alias.name in target_aliases:
43-
alias.delete()
44-
else:
45-
print "Merging alias: {}".format(alias.name)
46-
alias.person = target
47-
alias.save()
48-
49-
# merge DocEvents
50-
for docevent in source.docevent_set.all():
51-
docevent.by = target
52-
docevent.save()
53-
54-
# merge SubmissionEvents
55-
for subevent in source.submissionevent_set.all():
56-
subevent.by = target
57-
subevent.save()
58-
59-
# merge Messages
60-
for message in source.message_set.all():
61-
message.by = target
62-
message.save()
63-
64-
# merge Constraints
65-
for constraint in source.constraint_set.all():
66-
constraint.person = target
67-
constraint.save()
68-
69-
# merge Roles
70-
for role in source.role_set.all():
71-
role.person = target
72-
role.save()
73-
74-
# check for any remaining relationships and delete if none
75-
objs = [source]
76-
opts = Person._meta
77-
user = User.objects.filter(is_superuser=True).first()
78-
admin_site = admin.site
79-
using = 'default'
80-
81-
deletable_objects, perms_needed, protected = admin.utils.get_deleted_objects(
82-
objs, opts, user, admin_site, using)
83-
84-
if len(deletable_objects) > 1:
85-
print "Not Deleting Person: {}({})".format(source.ascii,source.pk)
86-
print "Related objects remain:"
87-
pprint.pprint(deletable_objects[1])
88-
89-
else:
90-
print "Deleting Person: {}({})".format(source.ascii,source.pk)
91-
source.delete()
32+
merge_persons(source,target,sys.stdout)

ietf/dbtemplate/factories.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import factory
2+
3+
from ietf.dbtemplate.models import DBTemplate
4+
5+
class DBTemplateFactory(factory.DjangoModelFactory):
6+
class Meta:
7+
model = DBTemplate
8+

ietf/dbtemplate/fixtures/nomcom_templates.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,10 @@ Questionnaire</field>
7676
<field to="group.group" name="group" rel="ManyToOneRel"><None></None></field>
7777
</object>
7878
<object pk="6" model="dbtemplate.dbtemplate">
79-
<field type="CharField" name="path">/nomcom/defaults/position/requirements.txt</field>
79+
<field type="CharField" name="path">/nomcom/defaults/position/requirements</field>
8080
<field type="CharField" name="title">Position requirements</field>
8181
<field type="TextField" name="variables">$position: Position</field>
82-
<field to="name.dbtemplatetypename" name="type" rel="ManyToOneRel">plain</field>
82+
<field to="name.dbtemplatetypename" name="type" rel="ManyToOneRel">rst</field>
8383
<field type="TextField" name="content">These are the requirements for the position $position:
8484

8585
Requirements.</field>
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{% extends "ietf.html" %}
2+
3+
{% load bootstrap3 %}
4+
5+
{% block content %}
6+
<h1>Template: {{ template }}</h1>
7+
8+
<h2>Meta information</h2>
9+
<dl>
10+
<dt>Title</dt>
11+
<dd>{{ template.title }}</dt>
12+
<dt>Group</dt>
13+
<dd>{{ template.group }}</dd>
14+
<dt>Template type</dt>
15+
<dd>{{ template.type.name }}
16+
{% if template.type.slug == "rst" %}
17+
<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>
18+
<p class="help-block">You can do variable interpolation with $varialbe if the template allows any variable.</p>
19+
{% endif %}
20+
{% if template.type.slug == "django" %}
21+
<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>
22+
<p class="help-block">You can do variable interpolation with the current django markup &#123;&#123;variable&#125;&#125; if the template allows any variable.</p>
23+
{% endif %}
24+
{% if template.type.slug == "plain" %}
25+
<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>
26+
{% endif %}
27+
</dd>
28+
{% if template.variables %}
29+
<dt>Variables allowed in this template</dt>
30+
<dd>{{ template.variables|linebreaks }}</dd>
31+
{% endif %}
32+
</dl>
33+
34+
<h2>Template content</h2>
35+
36+
<div class = "panel panel-default">
37+
<p class='pasted'>{{ template.content }}</p>
38+
</div>
39+
40+
{% endblock content %}

ietf/dbtemplate/views.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,19 @@ def template_edit(request, acronym, template_id, base_template='dbtemplate/templ
4343
}
4444
context.update(extra_context)
4545
return render(request, base_template, context)
46+
47+
def template_show(request, acronym, template_id, base_template='dbtemplate/template_edit.html', extra_context=None):
48+
group = get_object_or_404(Group, acronym=acronym)
49+
chairs = group.role_set.filter(name__slug='chair')
50+
extra_context = extra_context or {}
51+
52+
if not has_role(request.user, "Secretariat") and not chairs.filter(person__user=request.user).count():
53+
return HttpResponseForbidden("You are not authorized to access this view")
54+
55+
template = get_object_or_404(DBTemplate, id=template_id, group=group)
56+
57+
context = {'template': template,
58+
'group': group,
59+
}
60+
context.update(extra_context)
61+
return render(request, base_template, context)

ietf/doc/tests_draft.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1302,6 +1302,7 @@ def setUp(self):
13021302
expires=datetime.datetime.now() + datetime.timedelta(days=settings.INTERNET_DRAFT_DAYS_TO_EXPIRE),
13031303
group=mars_wg,
13041304
)
1305+
self.basea.documentauthor_set.create(author=Email.objects.create(address="basea_author@example.com"),order=1)
13051306

13061307
self.baseb = Document.objects.create(
13071308
name="draft-test-base-b",
@@ -1312,6 +1313,7 @@ def setUp(self):
13121313
expires=datetime.datetime.now() - datetime.timedelta(days = 365 - settings.INTERNET_DRAFT_DAYS_TO_EXPIRE),
13131314
group=mars_wg,
13141315
)
1316+
self.baseb.documentauthor_set.create(author=Email.objects.create(address="baseb_author@example.com"),order=1)
13151317

13161318
self.replacea = Document.objects.create(
13171319
name="draft-test-replace-a",
@@ -1322,6 +1324,7 @@ def setUp(self):
13221324
expires=datetime.datetime.now() + datetime.timedelta(days = settings.INTERNET_DRAFT_DAYS_TO_EXPIRE),
13231325
group=mars_wg,
13241326
)
1327+
self.replacea.documentauthor_set.create(author=Email.objects.create(address="replacea_author@example.com"),order=1)
13251328

13261329
self.replaceboth = Document.objects.create(
13271330
name="draft-test-replace-both",
@@ -1332,6 +1335,7 @@ def setUp(self):
13321335
expires=datetime.datetime.now() + datetime.timedelta(days = settings.INTERNET_DRAFT_DAYS_TO_EXPIRE),
13331336
group=mars_wg,
13341337
)
1338+
self.replaceboth.documentauthor_set.create(author=Email.objects.create(address="replaceboth_author@example.com"),order=1)
13351339

13361340
self.basea.set_state(State.objects.get(used=True, type="draft", slug="active"))
13371341
self.baseb.set_state(State.objects.get(used=True, type="draft", slug="expired"))
@@ -1366,8 +1370,8 @@ def test_change_replaces(self):
13661370
self.assertTrue(not RelatedDocument.objects.filter(relationship='possibly-replaces', source=self.replacea))
13671371
self.assertEqual(len(outbox), 1)
13681372
self.assertTrue('replacement status updated' in outbox[-1]['Subject'])
1369-
self.assertTrue('base-a@' in outbox[-1]['To'])
1370-
self.assertTrue('replace-a@' in outbox[-1]['To'])
1373+
self.assertTrue('replacea_author@' in outbox[-1]['To'])
1374+
self.assertTrue('basea_author@' in outbox[-1]['To'])
13711375

13721376
empty_outbox()
13731377
# Post that says replaceboth replaces both base a and base b
@@ -1378,9 +1382,9 @@ def test_change_replaces(self):
13781382
self.assertEqual(Document.objects.get(name='draft-test-base-a').get_state().slug,'repl')
13791383
self.assertEqual(Document.objects.get(name='draft-test-base-b').get_state().slug,'repl')
13801384
self.assertEqual(len(outbox), 1)
1381-
self.assertTrue('base-a@' in outbox[-1]['To'])
1382-
self.assertTrue('base-b@' in outbox[-1]['To'])
1383-
self.assertTrue('replace-both@' in outbox[-1]['To'])
1385+
self.assertTrue('basea_author@' in outbox[-1]['To'])
1386+
self.assertTrue('baseb_author@' in outbox[-1]['To'])
1387+
self.assertTrue('replaceboth_author@' in outbox[-1]['To'])
13841388

13851389
# Post that undoes replaceboth
13861390
empty_outbox()
@@ -1389,18 +1393,18 @@ def test_change_replaces(self):
13891393
self.assertEqual(Document.objects.get(name='draft-test-base-a').get_state().slug,'repl') # Because A is still also replaced by replacea
13901394
self.assertEqual(Document.objects.get(name='draft-test-base-b').get_state().slug,'expired')
13911395
self.assertEqual(len(outbox), 1)
1392-
self.assertTrue('base-a@' in outbox[-1]['To'])
1393-
self.assertTrue('base-b@' in outbox[-1]['To'])
1394-
self.assertTrue('replace-both@' in outbox[-1]['To'])
1396+
self.assertTrue('basea_author@' in outbox[-1]['To'])
1397+
self.assertTrue('baseb_author@' in outbox[-1]['To'])
1398+
self.assertTrue('replaceboth_author@' in outbox[-1]['To'])
13951399

13961400
# Post that undoes replacea
13971401
empty_outbox()
13981402
url = urlreverse('doc_change_replaces', kwargs=dict(name=self.replacea.name))
13991403
r = self.client.post(url, dict(replaces=""))
14001404
self.assertEqual(r.status_code, 302)
14011405
self.assertEqual(Document.objects.get(name='draft-test-base-a').get_state().slug,'active')
1402-
self.assertTrue('base-a@' in outbox[-1]['To'])
1403-
self.assertTrue('replace-a@' in outbox[-1]['To'])
1406+
self.assertTrue('basea_author@' in outbox[-1]['To'])
1407+
self.assertTrue('replacea_author@' in outbox[-1]['To'])
14041408

14051409

14061410
def test_review_possibly_replaces(self):

ietf/doc/utils.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
from ietf.utils.mail import send_mail
2121
from ietf.mailtrigger.utils import gather_address_lists
2222

23+
import debug # pyflakes:ignore
24+
2325
#TODO FIXME - it would be better if this lived in ietf/doc/mails.py, but there's
2426
# an import order issue to work out.
2527
def email_update_telechat(request, doc, text):

ietf/group/factories.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import factory
2+
3+
from ietf.group.models import Group
4+
5+
class GroupFactory(factory.DjangoModelFactory):
6+
class Meta:
7+
model = Group
8+
9+
name = factory.Faker('sentence',nb_words=6)
10+
acronym = factory.Faker('word')

ietf/ietfauth/utils.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,9 @@ def has_role(user, role_names, *args, **kwargs):
6464
"RG Secretary": Q(person=person,name="secr", group__type="rg", group__state__in=["active","proposed"]),
6565
"AG Secretary": Q(person=person,name="secr", group__type="ag", group__state__in=["active"]),
6666
"Team Chair": Q(person=person,name="chair", group__type="team", group__state="active"),
67-
"Nomcom Chair": Q(person=person, name="chair", group__type="nomcom", group__state="active", group__acronym__icontains=kwargs.get('year', '0000')),
68-
"Nomcom Advisor": Q(person=person, name="advisor", group__type="nomcom", group__state="active", group__acronym__icontains=kwargs.get('year', '0000')),
69-
"Nomcom": Q(person=person, group__type="nomcom", group__state="active", group__acronym__icontains=kwargs.get('year', '0000')),
67+
"Nomcom Chair": Q(person=person, name="chair", group__type="nomcom", group__acronym__icontains=kwargs.get('year', '0000')),
68+
"Nomcom Advisor": Q(person=person, name="advisor", group__type="nomcom", group__acronym__icontains=kwargs.get('year', '0000')),
69+
"Nomcom": Q(person=person, group__type="nomcom", group__acronym__icontains=kwargs.get('year', '0000')),
7070
"Liaison Manager": Q(person=person,name="liaiman",group__type="sdo",group__state="active", ),
7171
"Authorized Individual": Q(person=person,name="auth",group__type="sdo",group__state="active", ),
7272
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# -*- coding: utf-8 -*-
2+
from __future__ import unicode_literals
3+
4+
from django.db import migrations
5+
6+
def forward(apps, schema_editor):
7+
8+
Recipient=apps.get_model('mailtrigger','Recipient')
9+
MailTrigger=apps.get_model('mailtrigger','MailTrigger')
10+
11+
m = MailTrigger.objects.create(
12+
slug='person_merge_requested',
13+
desc="Recipients for a message requesting that duplicated Person records be merged ")
14+
m.to = Recipient.objects.filter(slug__in=['ietf_secretariat', ])
15+
16+
def reverse(apps, schema_editor):
17+
MailTrigger=apps.get_model('mailtrigger','MailTrigger')
18+
MailTrigger.objects.filter(slug='person_merge_requested').delete()
19+
20+
class Migration(migrations.Migration):
21+
22+
dependencies = [
23+
('mailtrigger', '0002_auto_20150809_1314'),
24+
]
25+
26+
operations = [
27+
migrations.RunPython(forward, reverse)
28+
]

0 commit comments

Comments
 (0)