Skip to content

Commit aae1356

Browse files
authored
Replace zip_choices with strict zip (#2064)
1 parent 439fb3e commit aae1356

File tree

6 files changed

+33
-31
lines changed

6 files changed

+33
-31
lines changed

evap/evaluation/models.py

+11-13
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
import secrets
33
import uuid
44
from collections import defaultdict
5+
from dataclasses import dataclass
56
from datetime import date, datetime, timedelta
67
from enum import Enum, auto
7-
from numbers import Number
8-
from typing import NamedTuple
8+
from numbers import Real
99

1010
from django.conf import settings
1111
from django.contrib import messages
@@ -1236,25 +1236,23 @@ def can_have_textanswers(self):
12361236
return self.is_text_question or self.is_rating_question and self.allows_additional_textanswers
12371237

12381238

1239-
# Let's deduplicate the fields here once mypy is smart enough to keep up with us :)
1240-
class Choices(NamedTuple):
1239+
@dataclass
1240+
class Choices:
12411241
css_class: str
1242-
values: tuple[Number]
1242+
values: tuple[Real]
12431243
colors: tuple[str]
1244-
grades: tuple[Number]
1244+
grades: tuple[Real]
12451245
names: list[StrOrPromise]
12461246
is_inverted: bool
12471247

1248+
def as_name_color_value_tuples(self):
1249+
return zip(self.names, self.colors, self.values, strict=True)
12481250

1249-
class BipolarChoices(NamedTuple):
1250-
css_class: str
1251-
values: tuple[Number]
1252-
colors: tuple[str]
1253-
grades: tuple[Number]
1254-
names: list[StrOrPromise]
1251+
1252+
@dataclass
1253+
class BipolarChoices(Choices):
12551254
plus_name: StrOrPromise
12561255
minus_name: StrOrPromise
1257-
is_inverted: bool
12581256

12591257

12601258
NO_ANSWER = 6

evap/evaluation/templatetags/evaluation_filters.py

+6-10
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from django.utils.translation import gettext_lazy as _
66

77
from evap.evaluation.models import BASE_UNIPOLAR_CHOICES, Contribution, Evaluation
8+
from evap.results.tools import RatingResult
89
from evap.rewards.tools import can_reward_points_be_used_by
910
from evap.student.forms import HeadingField
1011

@@ -76,12 +77,7 @@
7677

7778
@register.filter(name="zip")
7879
def _zip(a, b):
79-
return zip(a, b)
80-
81-
82-
@register.filter()
83-
def zip_choices(counts, choices):
84-
return zip(counts, choices.names, choices.colors, choices.values)
80+
return zip(a, b, strict=True)
8581

8682

8783
@register.filter
@@ -115,12 +111,12 @@ def percentage_one_decimal(fraction, population):
115111

116112

117113
@register.filter
118-
def to_colors(choices):
119-
if not choices:
114+
def to_colors(question_result: RatingResult | None):
115+
if question_result is None:
120116
# When displaying the course distribution, there are no associated voting choices.
121117
# In that case, we just use the colors of a unipolar scale.
122-
return BASE_UNIPOLAR_CHOICES["colors"]
123-
return choices.colors
118+
return BASE_UNIPOLAR_CHOICES["colors"][:-1]
119+
return question_result.colors
124120

125121

126122
@register.filter

evap/results/templates/distribution_with_grade.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<div class="distribution-bar {{ question_result.choices.css_class }}"
66
{% if question_result.question.is_bipolar_likert_question %} style="left: {{ question_result.minus_balance_count|percentage_one_decimal:question_result.count_sum }}"{% endif %}
77
>
8-
{% with colors=question_result.choices|to_colors %}
8+
{% with colors=question_result|to_colors %}
99
{% for value, color in distribution|zip:colors %}
1010
{% if value != 0 %}
1111
<div class="vote-bg-{{ color }}" style="width: {{ value|percentage_one_decimal:1 }};">&nbsp;</div>

evap/results/templates/evaluation_result_widget.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
data-bs-toggle="tooltip" data-bs-placement="left" data-fallback-placement='["left", "bottom"]'
88
title="{% spaceless %}{% include 'result_widget_tooltip.html' with question_result=course_or_evaluation.single_result_rating_result weight_info=course_or_evaluation|weight_info %}{% endspaceless %}"
99
>
10-
{% include "distribution_with_grade.html" with distribution=course_or_evaluation.distribution average=course_or_evaluation.avg_grade weight_info=course_or_evaluation|weight_info %}
10+
{% include "distribution_with_grade.html" with distribution=course_or_evaluation.distribution average=course_or_evaluation.avg_grade weight_info=course_or_evaluation|weight_info question_result=None %}
1111
</div>
1212
{% else %}
1313
<div class="d-flex" data-bs-toggle="tooltip" data-bs-placement="left" title="{% trans 'Not enough answers were given.' %}">

evap/results/templates/result_widget_tooltip.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
{% endif %}
1414
{% with question_result.question.is_bipolar_likert_question as is_bipolar %}
1515
<p>
16-
{% for count, name, color, value in question_result.counts|zip_choices:question_result.choices %}
16+
{% for count, name, color, value in question_result.zipped_choices %}
1717
<span class='vote-text-{{ color }} fas fa-fw-absolute
1818
{% if is_bipolar and value < 0 %}fa-caret-down pole-icon
1919
{% elif is_bipolar and value > 0 %}fa-caret-up pole-icon

evap/results/tools.py

+13-5
Original file line numberDiff line numberDiff line change
@@ -58,23 +58,31 @@ def is_published(cls, rating_result) -> TypeGuard["PublishedRatingResult"]:
5858
def has_answers(cls, rating_result) -> TypeGuard["AnsweredRatingResult"]:
5959
return isinstance(rating_result, AnsweredRatingResult)
6060

61-
def __init__(self, question, additional_text_result=None):
61+
def __init__(self, question, additional_text_result=None) -> None:
6262
assert question.is_rating_question
6363
self.question = discard_cached_related_objects(copy(question))
6464
self.additional_text_result = additional_text_result
65+
self.colors = tuple(
66+
color for _, color, value in self.choices.as_name_color_value_tuples() if value != NO_ANSWER
67+
)
6568

6669
@property
6770
def choices(self):
6871
return CHOICES[self.question.type]
6972

7073

7174
class PublishedRatingResult(RatingResult):
72-
def __init__(self, question, answer_counters, additional_text_result=None):
75+
def __init__(self, question, answer_counters, additional_text_result=None) -> None:
7376
super().__init__(question, additional_text_result)
74-
counts = OrderedDict((value, 0) for value in self.choices.values if value != NO_ANSWER)
77+
counts = OrderedDict(
78+
(value, [0, name, color, value]) for (name, color, value) in self.choices.as_name_color_value_tuples()
79+
)
80+
counts.pop(NO_ANSWER)
7581
for answer_counter in answer_counters:
76-
counts[answer_counter.answer] = answer_counter.count
77-
self.counts = tuple(counts.values())
82+
assert counts[answer_counter.answer][0] == 0
83+
counts[answer_counter.answer][0] = answer_counter.count
84+
self.counts = tuple(count for count, _, _, _ in counts.values())
85+
self.zipped_choices = tuple(counts.values())
7886

7987
@property
8088
def count_sum(self) -> int:

0 commit comments

Comments
 (0)