Skip to content

Commit

Permalink
Allow rounding values to be nullable
Browse files Browse the repository at this point in the history
In this case, the computed value will be returned as-is
  • Loading branch information
rolandgeider committed Nov 25, 2024
1 parent 47519f9 commit b952fb5
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 44 deletions.
28 changes: 24 additions & 4 deletions wger/manager/dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@
dataclass,
field,
)
from decimal import Decimal
from decimal import (
ROUND_DOWN,
Decimal,
)
from typing import (
Any,
List,
Expand Down Expand Up @@ -84,9 +87,6 @@ def text_repr(self) -> str:
This converts the values to something readable like "10 × 100 kg @ 2.00RiR"
"""

def round_value(x: int | float, base: float = 5) -> Decimal:
return normalize_decimal(Decimal(base * round(Decimal(x) / Decimal(base))))

out = []

if self.sets and self.sets > 1:
Expand Down Expand Up @@ -217,3 +217,23 @@ class RoutineLogData:
volume: GroupedLogData = field(default_factory=GroupedLogData)
intensity: GroupedLogData = field(default_factory=GroupedLogData)
sets: GroupedLogData = field(default_factory=GroupedLogData)


def round_value(
x: int | float | Decimal | None,
base: int | float | Decimal | None = None,
) -> Decimal | None:
"""
Rounds a value to the nearest base
If the base is None, the value will be returned as a Decimal object.
"""
if x is None:
return x

# If the result is an integer, remove the decimal part
result = Decimal(x) if base is None else Decimal(base * round(Decimal(x) / Decimal(base)))
if result == result.to_integral_value():
result = result.quantize(1, ROUND_DOWN)

return result
13 changes: 10 additions & 3 deletions wger/manager/migrations/0018_flexible_routines.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ class Migration(migrations.Migration):
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='sessions',
related_name='logs',
to='manager.workoutsession',
verbose_name='Session',
),
Expand Down Expand Up @@ -194,7 +194,12 @@ class Migration(migrations.Migration):
),
(
'repetition_rounding',
models.DecimalField(decimal_places=2, default=1, max_digits=4),
models.DecimalField(
decimal_places=2,
default=None,
max_digits=4,
null=True,
),
),
(
'slot',
Expand All @@ -217,8 +222,9 @@ class Migration(migrations.Migration):
'weight_rounding',
models.DecimalField(
decimal_places=2,
default=1.25,
default=None,
max_digits=4,
null=True,
),
),
(
Expand Down Expand Up @@ -355,6 +361,7 @@ class Migration(migrations.Migration):
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='sessions',
to='manager.routine',
),
),
Expand Down
29 changes: 19 additions & 10 deletions wger/manager/models/slot_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@
WeightUnit,
)
from wger.exercises.models import Exercise
from wger.manager.dataclasses import SetConfigData
from wger.manager.dataclasses import (
SetConfigData,
round_value,
)
from wger.manager.models.abstract_config import (
AbstractChangeConfig,
OperationChoices,
Expand Down Expand Up @@ -77,7 +80,8 @@ class SlotEntry(models.Model):
repetition_rounding = models.DecimalField(
decimal_places=2,
max_digits=4,
default=1,
default=None,
null=True,
)
"""
The amount by which the repetitions will be rounded
Expand All @@ -98,7 +102,8 @@ class SlotEntry(models.Model):
weight_rounding = models.DecimalField(
decimal_places=2,
max_digits=4,
default=1.25,
default=None,
null=True,
)
"""
The amount by which the weight will be rounded
Expand Down Expand Up @@ -276,22 +281,26 @@ def get_config(self, iteration: int) -> SetConfigData:
slot_entry_id=self.id,
exercise=self.exercise.id,
sets=sets if sets is not None else 1,
max_sets=max_sets,
weight=weight,
max_weight=max_weight if max_weight and weight and max_weight > weight else None,
max_sets=round_value(max_sets, 1),
weight=round_value(weight, self.weight_rounding),
max_weight=round_value(max_weight, self.weight_rounding)
if max_weight and weight and max_weight > weight
else None,
weight_rounding=self.weight_rounding if weight is not None else None,
# TODO: decide on whether to return None or always the unit
# weight_unit=self.weight_unit.pk if weight is not None else None,
weight_unit=self.weight_unit.pk,
reps=reps,
max_reps=max_reps if max_reps and reps and max_reps > reps else None,
reps=round_value(reps, self.repetition_rounding),
max_reps=round_value(max_reps, self.repetition_rounding)
if max_reps and reps and max_reps > reps
else None,
reps_rounding=self.repetition_rounding if reps is not None else None,
reps_unit=self.repetition_unit.pk,
# TODO: decide on whether to return None or always the unit
# reps_unit=self.repetition_unit.pk if reps is not None else None,
rir=self.get_rir(iteration),
rest=rest,
max_rest=max_rest if max_rest and rest and max_rest > rest else None,
rest=round_value(rest, 1),
max_rest=round_value(max_rest, 1) if max_rest and rest and max_rest > rest else None,
type=str(self.type),
comment=self.comment,
)
Expand Down
40 changes: 40 additions & 0 deletions wger/manager/tests/test_dataclasses_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# This file is part of wger Workout Manager.
#
# wger Workout Manager is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# wger Workout Manager is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License

# Standard Library
from decimal import Decimal

# Django
from django.test import SimpleTestCase

# wger
from wger.manager.dataclasses import round_value


class RoundValueTestCase(SimpleTestCase):
"""
Test that the rounding helper works as expected
"""

def test_round_value(self):
self.assertEqual(round_value(5.1, 5), 5)

def test_round_value2(self):
self.assertEqual(round_value(Decimal('7'), 1.25), Decimal('7.5'))

def test_round_value_no_base(self):
self.assertEqual(round_value(Decimal('1.33')), Decimal('1.33'))

def test_round_no_value(self):
self.assertEqual(round_value(None), None)
59 changes: 32 additions & 27 deletions wger/manager/tests/test_slot_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
# Standard Library
from decimal import Decimal

# Django
from django.test import SimpleTestCase

# wger
from wger.core.tests.base_testcase import WgerTestCase
from wger.manager.dataclasses import SetConfigData
Expand Down Expand Up @@ -176,11 +179,11 @@ def test_weight_config_with_logs(self):
slot_entry_id=self.slot_entry.pk,
exercise=1,
sets=4,
weight=80,
weight_rounding=Decimal('2.5'),
reps=5,
weight=Decimal(80),
weight_rounding=Decimal(2.5),
reps=Decimal(4),
reps_rounding=2,
rir=2,
rir=Decimal(2),
rest=120,
),
)
Expand All @@ -191,11 +194,11 @@ def test_weight_config_with_logs(self):
slot_entry_id=self.slot_entry.pk,
exercise=1,
sets=4,
weight=80,
weight_rounding=Decimal('2.5'),
reps=5,
weight=Decimal(80),
weight_rounding=Decimal(2.5),
reps=Decimal(4),
reps_rounding=2,
rir=2,
rir=Decimal(2),
rest=120,
),
)
Expand All @@ -206,11 +209,11 @@ def test_weight_config_with_logs(self):
slot_entry_id=self.slot_entry.pk,
exercise=1,
sets=4,
weight=80,
weight=Decimal(80),
weight_rounding=Decimal('2.5'),
reps=5,
reps=Decimal(4),
reps_rounding=2,
rir=2,
rir=Decimal(2),
rest=120,
),
)
Expand All @@ -223,9 +226,9 @@ def test_weight_config_with_logs(self):
sets=4,
weight=Decimal(82.5),
weight_rounding=Decimal('2.5'),
reps=5,
reps=Decimal(4),
reps_rounding=2,
rir=2,
rir=Decimal(2),
rest=120,
),
)
Expand All @@ -236,11 +239,11 @@ def test_weight_config_with_logs(self):
slot_entry_id=self.slot_entry.pk,
exercise=1,
sets=4,
weight=42,
weight=Decimal('42.5'),
weight_rounding=Decimal('2.5'),
reps=5,
reps=Decimal(4),
reps_rounding=2,
rir=2,
rir=Decimal(2),
rest=120,
),
)
Expand All @@ -251,11 +254,11 @@ def test_weight_config_with_logs(self):
slot_entry_id=self.slot_entry.pk,
exercise=1,
sets=4,
weight=42,
weight=Decimal(42.5),
weight_rounding=Decimal('2.5'),
reps=5,
reps=Decimal(4),
reps_rounding=2,
rir=2,
rir=Decimal(2),
rest=120,
),
)
Expand Down Expand Up @@ -313,11 +316,11 @@ def test_weight_config_with_logs_and_range(self):
slot_entry_id=self.slot_entry.pk,
exercise=1,
sets=1,
weight=80,
max_weight=100,
weight=Decimal(80),
max_weight=Decimal(100),
weight_rounding=Decimal('2.5'),
reps=5,
max_reps=6,
reps=Decimal(4),
max_reps=Decimal(6),
reps_rounding=2,
rir=None,
rest=None,
Expand All @@ -330,11 +333,11 @@ def test_weight_config_with_logs_and_range(self):
slot_entry_id=self.slot_entry.pk,
exercise=1,
sets=1,
weight=80,
max_weight=100,
weight=Decimal(80),
max_weight=Decimal(100),
weight_rounding=Decimal('2.5'),
reps=5,
max_reps=6,
reps=Decimal(4),
max_reps=Decimal(6),
reps_rounding=2,
rir=None,
rest=None,
Expand Down Expand Up @@ -397,6 +400,8 @@ def test_empty_configs(self):
),
)


class SlotEntryDuplicateConfigTestCase(SimpleTestCase):
def test_duplicate_configs(self):
configs = [
WeightConfig(
Expand Down

0 comments on commit b952fb5

Please sign in to comment.