Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
2ede70e
fix: Clean up view_feedback_pending
pselkirk Jul 21, 2023
3a6d70c
feat: Reclassify nomcom feedback (#4669)
pselkirk Jul 21, 2023
c4bf6d7
fix: Remove unused local variables
pselkirk Jul 21, 2023
2316db4
fix: Fix some missing and mis-nested html
pselkirk Jul 22, 2023
b419c28
test: Add tests for reclassifying feedback
pselkirk Jul 22, 2023
efce5b4
refactor: Substantial redesign of feedback reclassification
pselkirk Jul 25, 2023
9f79467
fix: Remember to clear the feedback associations when reclassifying
pselkirk Jul 26, 2023
0b373d2
feat: Add an 'Overcome by events' feedback type
pselkirk Jul 26, 2023
842e730
refactor: When invoking reclassification from a view-feedback page, l…
pselkirk Jul 26, 2023
5333359
Merge branch 'main' into fix-4669
pselkirk Jul 26, 2023
cd0e164
fix: De-conflict migration with 0004_statements
pselkirk Jul 26, 2023
6b57202
fix: Fix a test case to account for new feedback type
pselkirk Jul 27, 2023
3ef8a69
fix: 842e730 broke the Back button
pselkirk Jul 27, 2023
6060445
refactor: Reclassify feedback directly instead of putting it back in …
pselkirk Jul 27, 2023
4cb0c3b
fix: Adjust tests to new workflow
pselkirk Jul 27, 2023
a19fb30
refactor: Further refine reclassification to avoid redirects
pselkirk Jul 27, 2023
6d87c59
refactor: Impose a FeedbackTypeName ordering
pselkirk Jul 29, 2023
120cb6c
Merge branch 'main' into fix-4669
pselkirk Aug 5, 2023
d32d0b1
refactor: Merge reclassify_feedback_* back into view_feedback_*
pselkirk Aug 5, 2023
eb764fe
refactor: Add filter(used=True) on FeedbackTypeName querysets
pselkirk Aug 5, 2023
aec27e6
refactor: Add the new FeedbackTypeName to the reclassification succes…
pselkirk Aug 5, 2023
764a8f7
fix: Secure reclassification against rogue nomcom members
pselkirk Aug 7, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 21 additions & 5 deletions ietf/name/fixtures/names.json
Original file line number Diff line number Diff line change
Expand Up @@ -11113,8 +11113,9 @@
{
"fields": {
"desc": "",
"legend": "C",
"name": "Comment",
"order": 0,
"order": 1,
"used": true
},
"model": "name.feedbacktypename",
Expand All @@ -11123,8 +11124,9 @@
{
"fields": {
"desc": "",
"legend": "J",
"name": "Junk",
"order": 0,
"order": 5,
"used": true
},
"model": "name.feedbacktypename",
Expand All @@ -11133,8 +11135,9 @@
{
"fields": {
"desc": "",
"legend": "N",
"name": "Nomination",
"order": 0,
"order": 2,
"used": true
},
"model": "name.feedbacktypename",
Expand All @@ -11143,8 +11146,20 @@
{
"fields": {
"desc": "",
"legend": "O",
"name": "Overcome by events",
"order": 4,
"used": true
},
"model": "name.feedbacktypename",
"pk": "obe"
},
{
"fields": {
"desc": "",
"legend": "Q",
"name": "Questionnaire response",
"order": 0,
"order": 3,
"used": true
},
"model": "name.feedbacktypename",
Expand All @@ -11153,8 +11168,9 @@
{
"fields": {
"desc": "",
"legend": "R",
"name": "Read",
"order": 0,
"order": 6,
"used": true
},
"model": "name.feedbacktypename",
Expand Down
20 changes: 20 additions & 0 deletions ietf/name/migrations/0005_feedbacktypename_schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Copyright The IETF Trust 2023, All Rights Reserved

from django.db import migrations, models

class Migration(migrations.Migration):
dependencies = [
("name", "0004_statements"),
]

operations = [
migrations.AddField(
model_name="FeedbackTypeName",
name="legend",
field=models.CharField(
default="",
help_text="One-character legend for feedback classification form",
max_length=1,
),
),
]
36 changes: 36 additions & 0 deletions ietf/name/migrations/0006_feedbacktypename_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Copyright The IETF Trust 2023, All Rights Reserved

from django.db import migrations

def forward(apps, schema_editor):
FeedbackTypeName = apps.get_model("name", "FeedbackTypeName")
FeedbackTypeName.objects.create(slug="obe", name="Overcome by events")
for slug, legend, order in (
('comment', 'C', 1),
('nomina', 'N', 2),
('questio', 'Q', 3),
('obe', 'O', 4),
('junk', 'J', 5),
('read', 'R', 6),
):
ft = FeedbackTypeName.objects.get(slug=slug)
ft.legend = legend
ft.order = order
ft.save()

def reverse(apps, schema_editor):
FeedbackTypeName = apps.get_model("name", "FeedbackTypeName")
FeedbackTypeName.objects.filter(slug="obe").delete()
for ft in FeedbackTypeName.objects.all():
ft.legend = ""
ft.order = 0
ft.save()

class Migration(migrations.Migration):
dependencies = [
("name", "0005_feedbacktypename_schema"),
]

operations = [
migrations.RunPython(forward, reverse),
]
1 change: 1 addition & 0 deletions ietf/name/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ class NomineePositionStateName(NameModel):
"""Status of a candidate for a position: None, Accepted, Declined"""
class FeedbackTypeName(NameModel):
"""Type of feedback: questionnaires, nominations, comments"""
legend = models.CharField(max_length=1, default="", help_text="One-character legend for feedback classification form")
class DBTemplateTypeName(NameModel):
"""reStructuredText, Plain, Django"""
class DraftSubmissionStateName(NameModel):
Expand Down
2 changes: 1 addition & 1 deletion ietf/nomcom/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -653,7 +653,7 @@ def clean_key(self):

class PendingFeedbackForm(forms.ModelForm):

type = forms.ModelChoiceField(queryset=FeedbackTypeName.objects.all().order_by('pk'), widget=forms.RadioSelect, empty_label='Unclassified', required=False)
type = forms.ModelChoiceField(queryset=FeedbackTypeName.objects.all(), widget=forms.RadioSelect, empty_label='Unclassified', required=False)

class Meta:
model = Feedback
Expand Down
91 changes: 90 additions & 1 deletion ietf/nomcom/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -1450,7 +1450,7 @@ def test_feedback_index_totals(self):
self.assertEqual(response.status_code,200)
q = PyQuery(response.content)
r = q('tfoot').eq(0).find('td').contents()
self.assertEqual([a.strip() for a in r], ['1', '1', '1'])
self.assertEqual([a.strip() for a in r], ['1', '1', '1', '0'])

class FeedbackLastSeenTests(TestCase):

Expand Down Expand Up @@ -2863,3 +2863,92 @@ def test_decorate_volunteers_with_qualifications(self):
self.assertEqual(v.qualifications,'path_2')
if v.person == author_person:
self.assertEqual(v.qualifications,'path_3')

class ReclassifyFeedbackTests(TestCase):
"""Tests for feedback reclassification"""

def setUp(self):
super().setUp()
setup_test_public_keys_dir(self)
nomcom_test_data()
self.nc = NomComFactory.create(**nomcom_kwargs_for_year())
self.chair = self.nc.group.role_set.filter(name='chair').first().person
self.member = self.nc.group.role_set.filter(name='member').first().person
self.nominee = self.nc.nominee_set.order_by('pk').first()
self.position = self.nc.position_set.first()
self.topic = self.nc.topic_set.first()

def tearDown(self):
teardown_test_public_keys_dir(self)
super().tearDown()

def test_reclassify_feedback_nominee(self):
fb = FeedbackFactory.create(nomcom=self.nc,type_id='comment')
fb.positions.add(self.position)
fb.nominees.add(self.nominee)
fb.save()
self.assertEqual(Feedback.objects.comments().count(), 1)

url = reverse('ietf.nomcom.views.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.post(url, {'feedback_id': fb.id, 'type': 'obe'})
self.assertEqual(response.status_code, 403)

self.client.logout()
self.client.login(username=self.chair.user.username, password=self.chair.user.username + "+password")
provide_private_key_to_test_client(self)

response = self.client.post(url, {'feedback_id': fb.id, 'type': 'obe'})
self.assertEqual(response.status_code, 200)

fb = Feedback.objects.get(id=fb.id)
self.assertEqual(fb.type_id,'obe')
self.assertEqual(Feedback.objects.comments().count(), 0)
self.assertEqual(Feedback.objects.filter(type='obe').count(), 1)

def test_reclassify_feedback_topic(self):
fb = FeedbackFactory.create(nomcom=self.nc,type_id='comment')
fb.topics.add(self.topic)
fb.save()
self.assertEqual(Feedback.objects.comments().count(), 1)

url = reverse('ietf.nomcom.views.view_feedback_topic', kwargs={'year':self.nc.year(), 'topic_id':self.topic.id})
login_testing_unauthorized(self,self.member.user.username,url)
provide_private_key_to_test_client(self)
response = self.client.post(url, {'feedback_id': fb.id, 'type': 'unclassified'})
self.assertEqual(response.status_code, 403)

self.client.logout()
self.client.login(username=self.chair.user.username, password=self.chair.user.username + "+password")
provide_private_key_to_test_client(self)

response = self.client.post(url, {'feedback_id': fb.id, 'type': 'unclassified'})
self.assertEqual(response.status_code, 200)

fb = Feedback.objects.get(id=fb.id)
self.assertEqual(fb.type_id,None)
self.assertEqual(Feedback.objects.comments().count(), 0)
self.assertEqual(Feedback.objects.filter(type=None).count(), 1)

def test_reclassify_feedback_unrelated(self):
fb = FeedbackFactory(nomcom=self.nc, type_id='read')
self.assertEqual(Feedback.objects.filter(type='read').count(), 1)

url = reverse('ietf.nomcom.views.view_feedback_unrelated', kwargs={'year':self.nc.year()})
login_testing_unauthorized(self,self.member.user.username,url)
provide_private_key_to_test_client(self)
response = self.client.post(url, {'feedback_id': fb.id, 'type': 'junk'})
self.assertEqual(response.status_code, 403)

self.client.logout()
self.client.login(username=self.chair.user.username, password=self.chair.user.username + "+password")
provide_private_key_to_test_client(self)

response = self.client.post(url, {'feedback_id': fb.id, 'type': 'junk'})
self.assertEqual(response.status_code, 200)

fb = Feedback.objects.get(id=fb.id)
self.assertEqual(fb.type_id, 'junk')
self.assertEqual(Feedback.objects.filter(type='read').count(), 0)
self.assertEqual(Feedback.objects.filter(type='junk').count(), 1)
Loading