Skip to content

Commit f566a83

Browse files
committed
Captured when nomcom members reviewed feedback for a given nominee last. Added badges when new feedback is available. Improved layout of feedback index page. Fixes ietf-tools#1850.
- Legacy-Id: 10535
1 parent d65987b commit f566a83

9 files changed

Lines changed: 162 additions & 14 deletions

File tree

ietf/nomcom/factories.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import factory
22
import random
33

4-
from ietf.nomcom.models import NomCom, Position, Nominee, NomineePosition
4+
from ietf.nomcom.models import NomCom, Position, Feedback, Nominee, NomineePosition
55
from ietf.group.factories import GroupFactory
66
from ietf.person.factories import PersonFactory
77

@@ -128,3 +128,17 @@ class Meta:
128128
description = factory.Faker('paragraph',nb_sentences=4)
129129
is_open = True
130130

131+
class NomineeFactory(factory.DjangoModelFactory):
132+
class Meta:
133+
model = Nominee
134+
135+
nomcom = factory.SubFactory(NomComFactory)
136+
137+
class FeedbackFactory(factory.DjangoModelFactory):
138+
class Meta:
139+
model = Feedback
140+
141+
nomcom = factory.SubFactory(NomComFactory)
142+
subject = factory.Faker('sentence')
143+
comments = factory.Faker('paragraph')
144+
type_id = 'comment'

ietf/nomcom/migrations/0006_improve_default_questionnaire_templates.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# -*- coding: utf-8 -*-
22
from __future__ import unicode_literals
33

4-
from django.db import models, migrations
4+
from django.db import migrations
55

66

77
def set_new_template_content(apps, schema_editor):
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# -*- coding: utf-8 -*-
2+
from __future__ import unicode_literals
3+
4+
from django.db import models, migrations
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('person', '0004_auto_20150308_0440'),
11+
('nomcom', '0006_improve_default_questionnaire_templates'),
12+
]
13+
14+
operations = [
15+
migrations.CreateModel(
16+
name='FeedbackLastSeen',
17+
fields=[
18+
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
19+
('time', models.DateTimeField(auto_now=True)),
20+
('nominee', models.ForeignKey(to='nomcom.Nominee')),
21+
('reviewer', models.ForeignKey(to='person.Person')),
22+
],
23+
options={
24+
},
25+
bases=(models.Model,),
26+
),
27+
]

ietf/nomcom/models.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from django.template.loader import render_to_string
1010

1111
from ietf.nomcom.fields import EncryptedTextField
12-
from ietf.person.models import Email
12+
from ietf.person.models import Person,Email
1313
from ietf.group.models import Group
1414
from ietf.name.models import NomineePositionStateName, FeedbackTypeName
1515
from ietf.dbtemplate.models import DBTemplate
@@ -224,4 +224,7 @@ def __unicode__(self):
224224
class Meta:
225225
ordering = ['time']
226226

227-
227+
class FeedbackLastSeen(models.Model):
228+
reviewer = models.ForeignKey(Person)
229+
nominee = models.ForeignKey(Nominee)
230+
time = models.DateTimeField(auto_now=True)

ietf/nomcom/resources.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,17 @@ class Meta:
145145
}
146146
api.nomcom.register(NominationResource())
147147

148+
from ietf.person.resources import PersonResource
149+
class FeedbackLastSeenResource(ModelResource):
150+
reviewer = ToOneField(PersonResource, 'reviewer')
151+
nominee = ToOneField(NomineeResource, 'nominee')
152+
class Meta:
153+
queryset = FeedbackLastSeen.objects.all()
154+
serializer = api.Serializer()
155+
filtering = {
156+
"id": ALL,
157+
"time": ALL,
158+
"reviewer": ALL_WITH_RELATIONS,
159+
"nominee": ALL_WITH_RELATIONS,
160+
}
161+
api.nomcom.register(FeedbackLastSeenResource())

ietf/nomcom/tests.py

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,13 @@
2525
MEMBER_USER, SECRETARIAT_USER, EMAIL_DOMAIN, NOMCOM_YEAR
2626
from ietf.nomcom.models import NomineePosition, Position, Nominee, \
2727
NomineePositionStateName, Feedback, FeedbackTypeName, \
28-
Nomination
28+
Nomination, FeedbackLastSeen
2929
from ietf.nomcom.forms import EditMembersForm, EditMembersFormPreview
3030
from ietf.nomcom.utils import get_nomcom_by_year, get_or_create_nominee, get_hash_nominee_position
3131
from ietf.nomcom.management.commands.send_reminders import Command, is_time_to_send
3232

33-
from ietf.nomcom.factories import NomComFactory, nomcom_kwargs_for_year, provide_private_key_to_test_client
33+
from ietf.nomcom.factories import NomComFactory, FeedbackFactory, \
34+
nomcom_kwargs_for_year, provide_private_key_to_test_client
3435
from ietf.person.factories import PersonFactory
3536
from ietf.dbtemplate.factories import DBTemplateFactory
3637

@@ -1114,3 +1115,69 @@ def test_can_view_but_not_edit_templates(self):
11141115
q = PyQuery(response.content)
11151116
self.assertFalse( q('#templateform') )
11161117

1118+
class FeedbackLastSeenTests(TestCase):
1119+
1120+
def setUp(self):
1121+
self.nc = NomComFactory.create(**nomcom_kwargs_for_year(group__state_id='conclude'))
1122+
self.author = PersonFactory.create().email_set.first().address
1123+
self.member = self.nc.group.role_set.filter(name='member').first().person
1124+
self.nominee = self.nc.nominee_set.first()
1125+
self.position = self.nc.position_set.first()
1126+
for type_id in ['comment','nomina','questio']:
1127+
f = FeedbackFactory.create(author=self.author,nomcom=self.nc,type_id=type_id)
1128+
f.positions.add(self.position)
1129+
f.nominees.add(self.nominee)
1130+
now = datetime.datetime.now()
1131+
self.hour_ago = now - datetime.timedelta(hours=1)
1132+
self.half_hour_ago = now - datetime.timedelta(minutes=30)
1133+
self.second_from_now = now + datetime.timedelta(seconds=1)
1134+
1135+
def test_feedback_index_badges(self):
1136+
url = reverse('nomcom_view_feedback',kwargs={'year':self.nc.year()})
1137+
login_testing_unauthorized(self, self.member.user.username, url)
1138+
provide_private_key_to_test_client(self)
1139+
response = self.client.get(url)
1140+
self.assertEqual(response.status_code,200)
1141+
q = PyQuery(response.content)
1142+
self.assertEqual( len(q('.label-success')), 3 )
1143+
1144+
f = self.nc.feedback_set.first()
1145+
f.time = self.hour_ago
1146+
f.save()
1147+
FeedbackLastSeen.objects.create(reviewer=self.member,nominee=self.nominee)
1148+
FeedbackLastSeen.objects.update(time=self.half_hour_ago)
1149+
response = self.client.get(url)
1150+
self.assertEqual(response.status_code,200)
1151+
q = PyQuery(response.content)
1152+
self.assertEqual( len(q('.label-success')), 2 )
1153+
1154+
FeedbackLastSeen.objects.update(time=self.second_from_now)
1155+
response = self.client.get(url)
1156+
self.assertEqual(response.status_code,200)
1157+
q = PyQuery(response.content)
1158+
self.assertEqual( len(q('.label-success')), 0 )
1159+
1160+
def test_feedback_nominee_badges(self):
1161+
url = reverse('nomcom_view_feedback_nominee',kwargs={'year':self.nc.year(),'nominee_id':self.nominee.id})
1162+
login_testing_unauthorized(self, self.member.user.username, url)
1163+
provide_private_key_to_test_client(self)
1164+
response = self.client.get(url)
1165+
self.assertEqual(response.status_code,200)
1166+
q = PyQuery(response.content)
1167+
self.assertEqual( len(q('.label-success')), 3 )
1168+
1169+
f = self.nc.feedback_set.first()
1170+
f.time = self.hour_ago
1171+
f.save()
1172+
FeedbackLastSeen.objects.create(reviewer=self.member,nominee=self.nominee)
1173+
FeedbackLastSeen.objects.update(time=self.half_hour_ago)
1174+
response = self.client.get(url)
1175+
self.assertEqual(response.status_code,200)
1176+
q = PyQuery(response.content)
1177+
self.assertEqual( len(q('.label-success')), 2 )
1178+
1179+
FeedbackLastSeen.objects.update(time=self.second_from_now)
1180+
response = self.client.get(url)
1181+
self.assertEqual(response.status_code,200)
1182+
q = PyQuery(response.content)
1183+
self.assertEqual( len(q('.label-success')), 0 )

ietf/nomcom/views.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
PrivateKeyForm, EditNomcomForm, EditNomineeForm,
2727
PendingFeedbackForm, ReminderDatesForm, FullFeedbackFormSet,
2828
FeedbackEmailForm)
29-
from ietf.nomcom.models import Position, NomineePosition, Nominee, Feedback, NomCom, ReminderDates
29+
from ietf.nomcom.models import Position, NomineePosition, Nominee, Feedback, NomCom, ReminderDates, FeedbackLastSeen
3030
from ietf.nomcom.utils import (get_nomcom_by_year, store_nomcom_private_key,
3131
get_hash_nominee_position, send_reminder_to_nominees,
3232
HOME_TEMPLATE, NOMINEE_ACCEPT_REMINDER_TEMPLATE,NOMINEE_QUESTIONNAIRE_REMINDER_TEMPLATE)
@@ -579,7 +579,18 @@ def view_feedback(request, year):
579579
independent_feedback_types.append(ft)
580580
nominees_feedback = {}
581581
for nominee in nominees:
582-
nominee_feedback = [(ft.name, nominee.feedback_set.by_type(ft.slug).count()) for ft in feedback_types]
582+
last_seen = FeedbackLastSeen.objects.filter(reviewer=request.user.person,nominee=nominee).first()
583+
nominee_feedback = []
584+
for ft in feedback_types:
585+
qs = nominee.feedback_set.by_type(ft.slug)
586+
count = qs.count()
587+
if not count:
588+
newflag = False
589+
elif not last_seen:
590+
newflag = True
591+
else:
592+
newflag = qs.filter(time__gt=last_seen.time).exists()
593+
nominee_feedback.append( (ft.name,count,newflag) )
583594
nominees_feedback.update({nominee: nominee_feedback})
584595
independent_feedback = [ft.feedback_set.get_by_nomcom(nomcom).count() for ft in independent_feedback_types]
585596

@@ -736,11 +747,19 @@ def view_feedback_nominee(request, year, nominee_id):
736747
nominee = get_object_or_404(Nominee, id=nominee_id)
737748
feedback_types = FeedbackTypeName.objects.filter(slug__in=settings.NOMINEE_FEEDBACK_TYPES)
738749

750+
last_seen = FeedbackLastSeen.objects.filter(reviewer=request.user.person,nominee=nominee).first()
751+
last_seen_time = (last_seen and last_seen.time) or datetime.datetime(year=1,month=1,day=1)
752+
if last_seen:
753+
last_seen.save()
754+
else:
755+
FeedbackLastSeen.objects.create(reviewer=request.user.person,nominee=nominee)
756+
739757
return render_to_response('nomcom/view_feedback_nominee.html',
740758
{'year': year,
741759
'selected': 'view_feedback',
742760
'nominee': nominee,
743761
'feedback_types': feedback_types,
762+
'last_seen_time' : last_seen_time,
744763
'nomcom': nomcom}, RequestContext(request))
745764

746765

ietf/templates/nomcom/view_feedback.html

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,24 @@ <h2>Feedback related to nominees</h2>
1313
<table class="table table-condensed table-striped">
1414
<thead>
1515
<tr>
16-
<th>Nominee</th>
16+
<th class="col-sm-9">Nominee</th>
1717
{% for ft in feedback_types %}
18-
<th>{{ ft.name }}</th>
18+
<th class="col-sm-1 text-center">{{ ft.name }}</th>
1919
{% endfor %}
2020
</tr>
2121
</thead>
2222
<tbody>
23-
{% for nominee, feedback in nominees_feedback.items %}
23+
{% for nominee, feedback in nominees_feedback.items %}
2424
<tr>
2525
<td>
26-
<a href="{% url "nomcom_view_feedback_nominee" year nominee.id %}#comment">{{ nominee }}
26+
<a href="{% url "nomcom_view_feedback_nominee" year nominee.id %}">{{ nominee.name }}</a>
27+
<span class="hidden-xs">&lt;{{nominee.email.address}}&gt;</span>
2728
</td>
2829
{% for f in feedback %}
29-
<td>{{ f.1 }}</td>
30+
<td class="text-right">
31+
{% if f.2 %}<span class="label label-success">New</span>{% endif %}
32+
{{ f.1 }}
33+
</td>
3034
{% endfor %}
3135
</tr>
3236
{% endfor %}

ietf/templates/nomcom/view_feedback_nominee.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ <h2>Feedback about {{ nominee }} </h2>
2323
{% if feedback.type.slug == ft.slug %}
2424
{% if forloop.first %}<p></p>{% else %}<hr>{% endif %}
2525
<dl class="dl-horizontal">
26-
<dt>From</dt>
26+
<dt>{% if feedback.time > last_seen_time %}<span class="label label-success">New</span>{% endif %}From</dt>
2727
<dd>{{ feedback.author|formatted_email|default:"Anonymous" }}
2828
{% if ft.slug == "nomina" and feedback.nomination_set.first.share_nominator %}
2929
<span class="bg-info"> OK to share name with nominee</span>

0 commit comments

Comments
 (0)