Skip to content

Commit ac8e5f5

Browse files
committed
Make it possible to close nominations without closing feedback. Fixes ietf-tools#2255. Commit ready for merge.
- Legacy-Id: 13399
1 parent 1a18130 commit ac8e5f5

12 files changed

Lines changed: 193 additions & 20 deletions

ietf/nomcom/admin.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class NomineePositionAdmin(admin.ModelAdmin):
2424

2525

2626
class PositionAdmin(admin.ModelAdmin):
27-
list_display = ('name', 'nomcom', 'is_open')
27+
list_display = ('name', 'nomcom', 'is_open', 'accepting_nominations', 'accepting_feedback')
2828
list_filter = ('nomcom',)
2929

3030

ietf/nomcom/factories.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,8 @@ class Meta:
128128

129129
name = factory.Faker('sentence',nb_words=5)
130130
is_open = True
131+
accepting_nominations = True
132+
accepting_feedback = True
131133

132134
class NomineeFactory(factory.DjangoModelFactory):
133135
class Meta:

ietf/nomcom/forms.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class PositionNomineeField(forms.ChoiceField):
4141

4242
def __init__(self, *args, **kwargs):
4343
self.nomcom = kwargs.pop('nomcom')
44-
positions = Position.objects.get_by_nomcom(self.nomcom).opened().order_by('name')
44+
positions = Position.objects.get_by_nomcom(self.nomcom).filter(is_open=True).order_by('name')
4545
results = []
4646
for position in positions:
4747
accepted_nominees = [np.nominee for np in NomineePosition.objects.filter(position=position,state='accepted').exclude(nominee__duplicated__isnull=False)]
@@ -62,7 +62,7 @@ def clean(self, value):
6262
return nominee
6363
(position_id, nominee_id) = nominee.split('_')
6464
try:
65-
position = Position.objects.get_by_nomcom(self.nomcom).opened().get(id=position_id)
65+
position = Position.objects.get_by_nomcom(self.nomcom).filter(is_open=True).get(id=position_id)
6666
except Position.DoesNotExist:
6767
raise forms.ValidationError('Invalid nominee')
6868
try:
@@ -82,7 +82,7 @@ def clean(self, value):
8282
return nominee
8383
(position_id, nominee_id) = nominee.split('_')
8484
try:
85-
position = Position.objects.get_by_nomcom(self.nomcom).opened().get(id=position_id)
85+
position = Position.objects.get_by_nomcom(self.nomcom).filter(is_open=True).get(id=position_id)
8686
except Position.DoesNotExist:
8787
raise forms.ValidationError('Invalid nominee')
8888
try:
@@ -356,7 +356,9 @@ def __init__(self, *args, **kwargs):
356356
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()})
357357
self.fields['nominator_email'].label = 'Nominator email'
358358
if self.nomcom:
359-
self.fields['position'].queryset = Position.objects.get_by_nomcom(self.nomcom).opened()
359+
self.fields['position'].queryset = Position.objects.get_by_nomcom(self.nomcom).filter(is_open=True)
360+
if self.public:
361+
self.fields['position'].queryset = self.fields['position'].queryset.filter(accepting_nominations=True)
360362
self.fields['qualifications'].help_text = self.nomcom.initial_text
361363

362364
if not self.public:
@@ -454,7 +456,9 @@ def __init__(self, *args, **kwargs):
454456

455457
self.fields['nominator_email'].label = 'Nominator email'
456458
if self.nomcom:
457-
self.fields['position'].queryset = Position.objects.get_by_nomcom(self.nomcom).opened()
459+
self.fields['position'].queryset = Position.objects.get_by_nomcom(self.nomcom).filter(is_open=True)
460+
if self.public:
461+
self.fields['position'].queryset = self.fields['position'].queryset.filter(accepting_nominations=True)
458462
self.fields['qualifications'].help_text = self.nomcom.initial_text
459463

460464
if not self.public:
@@ -680,7 +684,7 @@ class PositionForm(forms.ModelForm):
680684

681685
class Meta:
682686
model = Position
683-
fields = ('name', 'is_open')
687+
fields = ('name', 'is_open', 'accepting_nominations', 'accepting_feedback')
684688

685689
def __init__(self, *args, **kwargs):
686690
self.nomcom = kwargs.pop('nomcom', None)
@@ -766,7 +770,7 @@ def set_nomcom(self, nomcom, user, instances=None):
766770
widget=forms.SelectMultiple(attrs={'class':'nominee_multi_select','size':'12'}),
767771
help_text='Hold down "Control", or "Command" on a Mac, to select more than one.')
768772
else:
769-
self.fields['position'] = forms.ModelChoiceField(queryset=Position.objects.get_by_nomcom(self.nomcom).opened(), label="Position")
773+
self.fields['position'] = forms.ModelChoiceField(queryset=Position.objects.get_by_nomcom(self.nomcom).filter(is_open=True), label="Position")
770774
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)
771775
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)
772776
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)
@@ -830,7 +834,6 @@ def save(self, commit=True):
830834
feedback.positions.add(position)
831835
return feedback
832836

833-
834837
FullFeedbackFormSet = forms.modelformset_factory(
835838
model=Feedback,
836839
extra=0,

ietf/nomcom/managers.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -68,14 +68,6 @@ class PositionQuerySet(QuerySet):
6868
def get_by_nomcom(self, nomcom):
6969
return self.filter(nomcom=nomcom)
7070

71-
def opened(self):
72-
""" only opened positions """
73-
return self.filter(is_open=True)
74-
75-
def closed(self):
76-
""" only closed positions """
77-
return self.filter(is_open=False)
78-
7971

8072
class PositionManager(models.Manager, MixinManager):
8173
def get_queryset(self):
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# -*- coding: utf-8 -*-
2+
# Generated by Django 1.10.7 on 2017-05-19 07:58
3+
from __future__ import unicode_literals
4+
5+
from django.db import migrations, models
6+
from django.db.models import F
7+
8+
def forward(apps, schema_editor):
9+
Position = apps.get_model('nomcom','Position')
10+
Position.objects.update(accepting_nominations=F('is_open'))
11+
Position.objects.update(accepting_feedback=F('is_open'))
12+
13+
def reverse(apps, schema_editor):
14+
pass
15+
16+
class Migration(migrations.Migration):
17+
18+
dependencies = [
19+
('nomcom', '0012_auto_20170210_0205'),
20+
]
21+
22+
operations = [
23+
migrations.AddField(
24+
model_name='position',
25+
name='accepting_feedback',
26+
field=models.BooleanField(default=False, verbose_name=b'Is accepting feedback'),
27+
),
28+
migrations.AddField(
29+
model_name='position',
30+
name='accepting_nominations',
31+
field=models.BooleanField(default=False, verbose_name=b'Is accepting nominations'),
32+
),
33+
migrations.RunPython(forward,reverse)
34+
]
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# -*- coding: utf-8 -*-
2+
# Generated by Django 1.10.7 on 2017-05-22 08:19
3+
from __future__ import unicode_literals
4+
5+
from django.db import migrations, models
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
dependencies = [
11+
('nomcom', '0013_position_nomination_feedback_switches'),
12+
]
13+
14+
operations = [
15+
migrations.AlterField(
16+
model_name='position',
17+
name='is_open',
18+
field=models.BooleanField(default=False, help_text=b'Set is_open when the nomcom is working on a position. Clear it when an appointment is confirmed.', verbose_name=b'Is open'),
19+
),
20+
]

ietf/nomcom/models.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,9 @@ class Position(models.Model):
163163
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"')
164164
requirement = models.ForeignKey(DBTemplate, related_name='requirement', null=True, editable=False)
165165
questionnaire = models.ForeignKey(DBTemplate, related_name='questionnaire', null=True, editable=False)
166-
is_open = models.BooleanField(verbose_name='Is open', default=False)
166+
is_open = models.BooleanField(verbose_name='Is open', default=False, help_text="Set is_open when the nomcom is working on a position. Clear it when an appointment is confirmed.")
167+
accepting_nominations = models.BooleanField(verbose_name='Is accepting nominations', default=False)
168+
accepting_feedback = models.BooleanField(verbose_name='Is accepting feedback', default=False)
167169

168170
objects = PositionManager()
169171

ietf/nomcom/resources.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ class Meta:
4242
"id": ALL,
4343
"name": ALL,
4444
"is_open": ALL,
45+
"accepting_nominations": ALL,
46+
"accepting_feedback": ALL,
4547
"nomcom": ALL_WITH_RELATIONS,
4648
"requirement": ALL_WITH_RELATIONS,
4749
"questionnaire": ALL_WITH_RELATIONS,

ietf/nomcom/test_data.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,9 @@ def nomcom_test_data():
130130
for name in POSITIONS:
131131
position, created = Position.objects.get_or_create(nomcom=nomcom,
132132
name=name,
133-
is_open=True)
133+
is_open=True,
134+
accepting_nominations=True,
135+
accepting_feedback=True)
134136

135137
ChangeStateGroupEvent.objects.get_or_create(group=group,
136138
type="changed_state",

ietf/nomcom/tests.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1771,3 +1771,93 @@ def test_merge_person(self):
17711771
self.assertEqual(self.nc.nominee_set.get(pk=self.nominee2.pk).feedback_set.count(),2)
17721772
self.assertFalse(self.nc.nominee_set.filter(pk=self.nominee1.pk).exists())
17731773

1774+
class AcceptingTests(TestCase):
1775+
def setUp(self):
1776+
build_test_public_keys_dir(self)
1777+
self.nc = NomComFactory(**nomcom_kwargs_for_year())
1778+
self.plain_person = PersonFactory.create()
1779+
self.member = self.nc.group.role_set.filter(name='member').first().person
1780+
1781+
def tearDown(self):
1782+
clean_test_public_keys_dir(self)
1783+
1784+
def test_public_accepting_nominations(self):
1785+
url = reverse('ietf.nomcom.views.public_nominate',kwargs={'year':self.nc.year()})
1786+
1787+
login_testing_unauthorized(self,self.plain_person.user.username,url)
1788+
response = self.client.get(url)
1789+
q=PyQuery(response.content)
1790+
self.assertEqual( len(q('#id_position option')) , 4 )
1791+
1792+
pos = self.nc.position_set.first()
1793+
pos.accepting_nominations=False
1794+
pos.save()
1795+
1796+
response = self.client.get(url)
1797+
q=PyQuery(response.content)
1798+
self.assertEqual( len(q('#id_position option')) , 3 )
1799+
1800+
def test_private_accepting_nominations(self):
1801+
url = reverse('ietf.nomcom.views.private_nominate',kwargs={'year':self.nc.year()})
1802+
1803+
login_testing_unauthorized(self,self.member.user.username,url)
1804+
response = self.client.get(url)
1805+
q=PyQuery(response.content)
1806+
self.assertEqual( len(q('#id_position option')) , 4 )
1807+
1808+
pos = self.nc.position_set.first()
1809+
pos.accepting_nominations=False
1810+
pos.save()
1811+
1812+
response = self.client.get(url)
1813+
q=PyQuery(response.content)
1814+
self.assertEqual( len(q('#id_position option')) , 4 )
1815+
1816+
def test_public_accepting_feedback(self):
1817+
url = reverse('ietf.nomcom.views.public_feedback',kwargs={'year':self.nc.year()})
1818+
1819+
login_testing_unauthorized(self,self.plain_person.user.username,url)
1820+
response = self.client.get(url)
1821+
q=PyQuery(response.content)
1822+
self.assertEqual( len(q('.badge')) , 3 )
1823+
1824+
pos = self.nc.position_set.first()
1825+
pos.accepting_feedback=False
1826+
pos.save()
1827+
1828+
response = self.client.get(url)
1829+
q=PyQuery(response.content)
1830+
self.assertEqual( len(q('.badge')) , 2 )
1831+
1832+
url += "?nominee=%d&position=%d" % (pos.nominee_set.first().pk, pos.pk)
1833+
response = self.client.get(url)
1834+
self.assertTrue('not currently accepting feedback' in unicontent(response))
1835+
1836+
test_data = {'comments': 'junk',
1837+
'position_name': pos.name,
1838+
'nominee_name': pos.nominee_set.first().email.person.name,
1839+
'nominee_email': pos.nominee_set.first().email.address,
1840+
'confirmation': False,
1841+
'nominator_email': self.plain_person.email().address,
1842+
'nominator_name': self.plain_person.plain_name(),
1843+
}
1844+
response = self.client.post(url, test_data)
1845+
self.assertTrue('not currently accepting feedback' in unicontent(response))
1846+
1847+
def test_private_accepting_feedback(self):
1848+
url = reverse('ietf.nomcom.views.private_feedback',kwargs={'year':self.nc.year()})
1849+
1850+
login_testing_unauthorized(self,self.member.user.username,url)
1851+
response = self.client.get(url)
1852+
q=PyQuery(response.content)
1853+
self.assertEqual( len(q('.badge')) , 3 )
1854+
1855+
pos = self.nc.position_set.first()
1856+
pos.accepting_feedback=False
1857+
pos.save()
1858+
1859+
response = self.client.get(url)
1860+
q=PyQuery(response.content)
1861+
self.assertEqual( len(q('.badge')) , 3 )
1862+
1863+

0 commit comments

Comments
 (0)