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 #1850.
- Legacy-Id: 10535
This commit is contained in:
parent
d65987b935
commit
f566a83d1d
|
@ -1,7 +1,7 @@
|
|||
import factory
|
||||
import random
|
||||
|
||||
from ietf.nomcom.models import NomCom, Position, Nominee, NomineePosition
|
||||
from ietf.nomcom.models import NomCom, Position, Feedback, Nominee, NomineePosition
|
||||
from ietf.group.factories import GroupFactory
|
||||
from ietf.person.factories import PersonFactory
|
||||
|
||||
|
@ -128,3 +128,17 @@ class PositionFactory(factory.DjangoModelFactory):
|
|||
description = factory.Faker('paragraph',nb_sentences=4)
|
||||
is_open = True
|
||||
|
||||
class NomineeFactory(factory.DjangoModelFactory):
|
||||
class Meta:
|
||||
model = Nominee
|
||||
|
||||
nomcom = factory.SubFactory(NomComFactory)
|
||||
|
||||
class FeedbackFactory(factory.DjangoModelFactory):
|
||||
class Meta:
|
||||
model = Feedback
|
||||
|
||||
nomcom = factory.SubFactory(NomComFactory)
|
||||
subject = factory.Faker('sentence')
|
||||
comments = factory.Faker('paragraph')
|
||||
type_id = 'comment'
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def set_new_template_content(apps, schema_editor):
|
||||
|
|
27
ietf/nomcom/migrations/0007_feedbacklastseen.py
Normal file
27
ietf/nomcom/migrations/0007_feedbacklastseen.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('person', '0004_auto_20150308_0440'),
|
||||
('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,),
|
||||
),
|
||||
]
|
|
@ -9,7 +9,7 @@ from django.contrib.auth.models import User
|
|||
from django.template.loader import render_to_string
|
||||
|
||||
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
|
||||
|
@ -224,4 +224,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)
|
||||
|
|
|
@ -145,3 +145,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())
|
||||
|
|
|
@ -25,12 +25,13 @@ from ietf.nomcom.test_data import nomcom_test_data, generate_cert, check_comment
|
|||
MEMBER_USER, SECRETARIAT_USER, EMAIL_DOMAIN, NOMCOM_YEAR
|
||||
from ietf.nomcom.models import NomineePosition, Position, Nominee, \
|
||||
NomineePositionStateName, Feedback, FeedbackTypeName, \
|
||||
Nomination
|
||||
Nomination, FeedbackLastSeen
|
||||
from ietf.nomcom.forms import EditMembersForm, EditMembersFormPreview
|
||||
from ietf.nomcom.utils import get_nomcom_by_year, get_or_create_nominee, get_hash_nominee_position
|
||||
from ietf.nomcom.management.commands.send_reminders import Command, is_time_to_send
|
||||
|
||||
from ietf.nomcom.factories import NomComFactory, nomcom_kwargs_for_year, provide_private_key_to_test_client
|
||||
from ietf.nomcom.factories import NomComFactory, FeedbackFactory, \
|
||||
nomcom_kwargs_for_year, provide_private_key_to_test_client
|
||||
from ietf.person.factories import PersonFactory
|
||||
from ietf.dbtemplate.factories import DBTemplateFactory
|
||||
|
||||
|
@ -1114,3 +1115,69 @@ class InactiveNomcomTests(TestCase):
|
|||
q = PyQuery(response.content)
|
||||
self.assertFalse( q('#templateform') )
|
||||
|
||||
class FeedbackLastSeenTests(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.nc = NomComFactory.create(**nomcom_kwargs_for_year(group__state_id='conclude'))
|
||||
self.author = PersonFactory.create().email_set.first().address
|
||||
self.member = self.nc.group.role_set.filter(name='member').first().person
|
||||
self.nominee = self.nc.nominee_set.first()
|
||||
self.position = self.nc.position_set.first()
|
||||
for type_id in ['comment','nomina','questio']:
|
||||
f = FeedbackFactory.create(author=self.author,nomcom=self.nc,type_id=type_id)
|
||||
f.positions.add(self.position)
|
||||
f.nominees.add(self.nominee)
|
||||
now = datetime.datetime.now()
|
||||
self.hour_ago = now - datetime.timedelta(hours=1)
|
||||
self.half_hour_ago = now - datetime.timedelta(minutes=30)
|
||||
self.second_from_now = now + datetime.timedelta(seconds=1)
|
||||
|
||||
def test_feedback_index_badges(self):
|
||||
url = reverse('nomcom_view_feedback',kwargs={'year':self.nc.year()})
|
||||
login_testing_unauthorized(self, self.member.user.username, url)
|
||||
provide_private_key_to_test_client(self)
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code,200)
|
||||
q = PyQuery(response.content)
|
||||
self.assertEqual( len(q('.label-success')), 3 )
|
||||
|
||||
f = self.nc.feedback_set.first()
|
||||
f.time = self.hour_ago
|
||||
f.save()
|
||||
FeedbackLastSeen.objects.create(reviewer=self.member,nominee=self.nominee)
|
||||
FeedbackLastSeen.objects.update(time=self.half_hour_ago)
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code,200)
|
||||
q = PyQuery(response.content)
|
||||
self.assertEqual( len(q('.label-success')), 2 )
|
||||
|
||||
FeedbackLastSeen.objects.update(time=self.second_from_now)
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code,200)
|
||||
q = PyQuery(response.content)
|
||||
self.assertEqual( len(q('.label-success')), 0 )
|
||||
|
||||
def test_feedback_nominee_badges(self):
|
||||
url = reverse('nomcom_view_feedback_nominee',kwargs={'year':self.nc.year(),'nominee_id':self.nominee.id})
|
||||
login_testing_unauthorized(self, self.member.user.username, url)
|
||||
provide_private_key_to_test_client(self)
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code,200)
|
||||
q = PyQuery(response.content)
|
||||
self.assertEqual( len(q('.label-success')), 3 )
|
||||
|
||||
f = self.nc.feedback_set.first()
|
||||
f.time = self.hour_ago
|
||||
f.save()
|
||||
FeedbackLastSeen.objects.create(reviewer=self.member,nominee=self.nominee)
|
||||
FeedbackLastSeen.objects.update(time=self.half_hour_ago)
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code,200)
|
||||
q = PyQuery(response.content)
|
||||
self.assertEqual( len(q('.label-success')), 2 )
|
||||
|
||||
FeedbackLastSeen.objects.update(time=self.second_from_now)
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code,200)
|
||||
q = PyQuery(response.content)
|
||||
self.assertEqual( len(q('.label-success')), 0 )
|
||||
|
|
|
@ -26,7 +26,7 @@ from ietf.nomcom.forms import (NominateForm, FeedbackForm, QuestionnaireForm,
|
|||
PrivateKeyForm, EditNomcomForm, EditNomineeForm,
|
||||
PendingFeedbackForm, ReminderDatesForm, FullFeedbackFormSet,
|
||||
FeedbackEmailForm)
|
||||
from ietf.nomcom.models import Position, NomineePosition, Nominee, Feedback, NomCom, ReminderDates
|
||||
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)
|
||||
|
@ -579,7 +579,18 @@ def view_feedback(request, year):
|
|||
independent_feedback_types.append(ft)
|
||||
nominees_feedback = {}
|
||||
for nominee in nominees:
|
||||
nominee_feedback = [(ft.name, nominee.feedback_set.by_type(ft.slug).count()) for ft in feedback_types]
|
||||
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.update({nominee: nominee_feedback})
|
||||
independent_feedback = [ft.feedback_set.get_by_nomcom(nomcom).count() for ft in independent_feedback_types]
|
||||
|
||||
|
@ -736,11 +747,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))
|
||||
|
||||
|
||||
|
|
|
@ -13,20 +13,24 @@
|
|||
<table class="table table-condensed table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nominee</th>
|
||||
<th class="col-sm-9">Nominee</th>
|
||||
{% for ft in feedback_types %}
|
||||
<th>{{ ft.name }}</th>
|
||||
<th class="col-sm-1 text-center">{{ ft.name }}</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for nominee, feedback in nominees_feedback.items %}
|
||||
{% for nominee, feedback in nominees_feedback.items %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{% url "nomcom_view_feedback_nominee" year nominee.id %}#comment">{{ nominee }}
|
||||
<a href="{% url "nomcom_view_feedback_nominee" year nominee.id %}">{{ nominee.name }}</a>
|
||||
<span class="hidden-xs"><{{nominee.email.address}}></span>
|
||||
</td>
|
||||
{% for f in feedback %}
|
||||
<td>{{ f.1 }}</td>
|
||||
<td class="text-right">
|
||||
{% if f.2 %}<span class="label label-success">New</span>{% endif %}
|
||||
{{ f.1 }}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue