Skip to content

Commit 6c8629c

Browse files
committed
Merged in [10629] from rjsparks@nostrum.com:
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 ietf-tools#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#1495. - 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. - Legacy-Id: 10703 Note: SVN reference [10629] has been migrated to Git commit c8bbfba
2 parents b88b835 + c8bbfba commit 6c8629c

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
@@ -1309,6 +1309,7 @@ def setUp(self):
13091309
expires=datetime.datetime.now() + datetime.timedelta(days=settings.INTERNET_DRAFT_DAYS_TO_EXPIRE),
13101310
group=mars_wg,
13111311
)
1312+
self.basea.documentauthor_set.create(author=Email.objects.create(address="basea_author@example.com"),order=1)
13121313

13131314
self.baseb = Document.objects.create(
13141315
name="draft-test-base-b",
@@ -1319,6 +1320,7 @@ def setUp(self):
13191320
expires=datetime.datetime.now() - datetime.timedelta(days = 365 - settings.INTERNET_DRAFT_DAYS_TO_EXPIRE),
13201321
group=mars_wg,
13211322
)
1323+
self.baseb.documentauthor_set.create(author=Email.objects.create(address="baseb_author@example.com"),order=1)
13221324

13231325
self.replacea = Document.objects.create(
13241326
name="draft-test-replace-a",
@@ -1329,6 +1331,7 @@ def setUp(self):
13291331
expires=datetime.datetime.now() + datetime.timedelta(days = settings.INTERNET_DRAFT_DAYS_TO_EXPIRE),
13301332
group=mars_wg,
13311333
)
1334+
self.replacea.documentauthor_set.create(author=Email.objects.create(address="replacea_author@example.com"),order=1)
13321335

13331336
self.replaceboth = Document.objects.create(
13341337
name="draft-test-replace-both",
@@ -1339,6 +1342,7 @@ def setUp(self):
13391342
expires=datetime.datetime.now() + datetime.timedelta(days = settings.INTERNET_DRAFT_DAYS_TO_EXPIRE),
13401343
group=mars_wg,
13411344
)
1345+
self.replaceboth.documentauthor_set.create(author=Email.objects.create(address="replaceboth_author@example.com"),order=1)
13421346

13431347
self.basea.set_state(State.objects.get(used=True, type="draft", slug="active"))
13441348
self.baseb.set_state(State.objects.get(used=True, type="draft", slug="expired"))
@@ -1373,8 +1377,8 @@ def test_change_replaces(self):
13731377
self.assertTrue(not RelatedDocument.objects.filter(relationship='possibly-replaces', source=self.replacea))
13741378
self.assertEqual(len(outbox), 1)
13751379
self.assertTrue('replacement status updated' in outbox[-1]['Subject'])
1376-
self.assertTrue('base-a@' in outbox[-1]['To'])
1377-
self.assertTrue('replace-a@' in outbox[-1]['To'])
1380+
self.assertTrue('replacea_author@' in outbox[-1]['To'])
1381+
self.assertTrue('basea_author@' in outbox[-1]['To'])
13781382

13791383
empty_outbox()
13801384
# Post that says replaceboth replaces both base a and base b
@@ -1385,9 +1389,9 @@ def test_change_replaces(self):
13851389
self.assertEqual(Document.objects.get(name='draft-test-base-a').get_state().slug,'repl')
13861390
self.assertEqual(Document.objects.get(name='draft-test-base-b').get_state().slug,'repl')
13871391
self.assertEqual(len(outbox), 1)
1388-
self.assertTrue('base-a@' in outbox[-1]['To'])
1389-
self.assertTrue('base-b@' in outbox[-1]['To'])
1390-
self.assertTrue('replace-both@' in outbox[-1]['To'])
1392+
self.assertTrue('basea_author@' in outbox[-1]['To'])
1393+
self.assertTrue('baseb_author@' in outbox[-1]['To'])
1394+
self.assertTrue('replaceboth_author@' in outbox[-1]['To'])
13911395

13921396
# Post that undoes replaceboth
13931397
empty_outbox()
@@ -1396,18 +1400,18 @@ def test_change_replaces(self):
13961400
self.assertEqual(Document.objects.get(name='draft-test-base-a').get_state().slug,'repl') # Because A is still also replaced by replacea
13971401
self.assertEqual(Document.objects.get(name='draft-test-base-b').get_state().slug,'expired')
13981402
self.assertEqual(len(outbox), 1)
1399-
self.assertTrue('base-a@' in outbox[-1]['To'])
1400-
self.assertTrue('base-b@' in outbox[-1]['To'])
1401-
self.assertTrue('replace-both@' in outbox[-1]['To'])
1403+
self.assertTrue('basea_author@' in outbox[-1]['To'])
1404+
self.assertTrue('baseb_author@' in outbox[-1]['To'])
1405+
self.assertTrue('replaceboth_author@' in outbox[-1]['To'])
14021406

14031407
# Post that undoes replacea
14041408
empty_outbox()
14051409
url = urlreverse('doc_change_replaces', kwargs=dict(name=self.replacea.name))
14061410
r = self.client.post(url, dict(replaces=""))
14071411
self.assertEqual(r.status_code, 302)
14081412
self.assertEqual(Document.objects.get(name='draft-test-base-a').get_state().slug,'active')
1409-
self.assertTrue('base-a@' in outbox[-1]['To'])
1410-
self.assertTrue('replace-a@' in outbox[-1]['To'])
1413+
self.assertTrue('basea_author@' in outbox[-1]['To'])
1414+
self.assertTrue('replacea_author@' in outbox[-1]['To'])
14111415

14121416

14131417
def test_review_possibly_replaces(self):

ietf/doc/utils.py

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

24+
import debug # pyflakes:ignore
25+
2426
#TODO FIXME - it would be better if this lived in ietf/doc/mails.py, but there's
2527
# an import order issue to work out.
2628
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)