diff --git a/wger/trophies/checkers/personal_record.py b/wger/trophies/checkers/personal_record.py index 80993db14..ea2b00aa1 100644 --- a/wger/trophies/checkers/personal_record.py +++ b/wger/trophies/checkers/personal_record.py @@ -15,6 +15,8 @@ # along with this program. If not, see . # Standard Library +import logging +from decimal import Decimal from typing import Optional # wger @@ -26,6 +28,9 @@ from wger.trophies.models.user_trophy import UserTrophy from .base import BaseTrophyChecker +logger = logging.getLogger(__name__) + + class PersonalRecordChecker(BaseTrophyChecker): """ Checker for Personal Record (PR) repeatable trophy. @@ -37,37 +42,33 @@ class PersonalRecordChecker(BaseTrophyChecker): """ - def _estimate_one_rep_max(self): + def _estimate_one_rep_max(self) -> float: """ - Brzycki's formula: 1RM = weight * (36 / (37 - repetitions)) - """ - log: WorkoutLog | None = self.params.get('log', None) + Estimates the user's one-rep max (1RM) using Brzycki's formula: + 1RM = weight * (36 / (37 - repetitions)) + Note: returning float because of serialization issues with Decimal in JSON. + """ + log: WorkoutLog | None = self.params.get('log') if not log: raise ValueError('Log should not be None') - weight = getattr(log, 'weight', None) - repetitions = getattr(log, 'repetitions', None) + weight = log.weight + repetitions = log.repetitions + rir = log.rir if weight is None: raise ValueError('Weight should not be None') if repetitions is None: raise ValueError('Repetitions should not be None') + if rir is not None: + repetitions += rir - try: - reps = int(repetitions) - except (TypeError, ValueError): - raise ValueError('Repetitions must be an integer') - - if reps == 37: + if repetitions == 37: raise ValueError("In Brzycki's formula, repetitions cannot be equal to 37.") - try: - w = float(weight) - except (TypeError, ValueError): - raise ValueError('Weight must be a number') - - return round(w * (36.0 / float(37 - reps)), 2) + result = weight * (Decimal('36') / (Decimal('37') - repetitions)) + return round(float(result), 2) def check(self) -> bool: """Check if user has beaten Personal Record.""" @@ -109,43 +110,41 @@ class PersonalRecordChecker(BaseTrophyChecker): return 100.0 if self.check() else 0.0 def get_context_data(self) -> Optional[dict]: - log = self.params.get('log', None) + log: WorkoutLog | None = self.params.get('log', None) if not log: return None - session = getattr(log, 'session', None) - exercise = getattr(log, 'exercise', None) - repetitions_unit = getattr(log, 'repetitions_unit', None) - weight_unit = getattr(log, 'weight_unit', None) - repetitions = getattr(log, 'repetitions', None) - weight = getattr(log, 'weight', None) + session = log.session + exercise = log.exercise + repetitions_unit = log.repetitions_unit + weight_unit = log.weight_unit + repetitions = log.repetitions + weight = log.weight try: one_rm_estimate = self._estimate_one_rep_max() except Exception as e: - print(f'PR estimation failed : {e}') + logger.warning(f'PR estimation failed : {e}') one_rm_estimate = None return { - 'log_id': getattr(log, 'id', None), - 'date': getattr(log, 'date', None).isoformat(), - 'session_id': getattr(session, 'id', None) if session else None, - 'exercise_id': getattr(exercise, 'id', None) if exercise else None, - 'repetitions_unit_id': getattr(repetitions_unit, 'id', None) - if repetitions_unit - else None, + 'log_id': log.id, + 'date': log.date.isoformat(), + 'session_id': session.id if session else None, + 'exercise_id': exercise.id if exercise else None, + 'repetitions_unit_id': repetitions_unit.id if repetitions_unit else None, 'repetitions': float(repetitions) if repetitions else None, - 'weight_unit_id': getattr(weight_unit, 'id', None) if weight_unit else None, + 'weight_unit_id': weight_unit.id if weight_unit else None, 'weight': float(weight) if weight else None, - 'iteration': getattr(log, 'iteration', None), + 'iteration': log.iteration, 'one_rep_max_estimate': one_rm_estimate, } - def get_target_value(self) -> int: + def get_target_value(self) -> str: return 'N/A' - def get_current_value(self) -> int: + def get_current_value(self) -> str: return 'N/A' def get_progress_display(self) -> str: diff --git a/wger/trophies/services/trophy.py b/wger/trophies/services/trophy.py index 53d33dfe1..45cbfae2f 100644 --- a/wger/trophies/services/trophy.py +++ b/wger/trophies/services/trophy.py @@ -35,6 +35,7 @@ from wger.trophies.models import ( UserTrophy, ) + logger = logging.getLogger(__name__) # Trophy settings from WGER_SETTINGS (defined in settings_global.py) diff --git a/wger/trophies/tests/test_checkers.py b/wger/trophies/tests/test_checkers.py index d896f12b2..12bd7f1df 100644 --- a/wger/trophies/tests/test_checkers.py +++ b/wger/trophies/tests/test_checkers.py @@ -26,6 +26,7 @@ from wger.exercises.models.category import ExerciseCategory from wger.manager.models.log import WorkoutLog from wger.trophies.checkers.date_based import DateBasedChecker from wger.trophies.checkers.inactivity_return import InactivityReturnChecker +from wger.trophies.checkers.personal_record import PersonalRecordChecker from wger.trophies.checkers.streak import StreakChecker from wger.trophies.checkers.time_based import TimeBasedChecker from wger.trophies.checkers.volume import VolumeChecker @@ -509,16 +510,10 @@ class PersonalRecordCheckerTestCase(WgerTestCase): ) def test_check_returns_false_with_no_logs(self): - # wger - from wger.trophies.checkers.personal_record import PersonalRecordChecker - checker = PersonalRecordChecker(self.user, self.trophy, {}) self.assertFalse(checker.check()) def test_first_log_counts_as_pr(self): - # wger - from wger.trophies.checkers.personal_record import PersonalRecordChecker - log = WorkoutLog(user=self.user, exercise=self.exercise, repetitions=10, weight=100) checker = PersonalRecordChecker(self.user, self.trophy, {'log': log}) @@ -528,9 +523,6 @@ class PersonalRecordCheckerTestCase(WgerTestCase): self.assertIsNotNone(context) def test_improvement_detected_and_context_values(self): - # wger - from wger.trophies.checkers.personal_record import PersonalRecordChecker - log1 = WorkoutLog(user=self.user, exercise=self.exercise, repetitions=10, weight=100) checker1 = PersonalRecordChecker(self.user, self.trophy, {'log': log1}) self.assertTrue(checker1.check()) @@ -543,9 +535,6 @@ class PersonalRecordCheckerTestCase(WgerTestCase): self.assertIsNotNone(context) def no_award_if_not_an_improvement(self): - # wger - from wger.trophies.checkers.personal_record import PersonalRecordChecker - log1 = WorkoutLog(user=self.user, exercise=self.exercise, repetitions=10, weight=100) checker1 = PersonalRecordChecker(self.user, self.trophy, {'log': log1}) self.assertTrue(checker1.check()) @@ -566,9 +555,6 @@ class PersonalRecordCheckerTestCase(WgerTestCase): self.assertFalse(checker4.check()) def test_estimate_1rm(self): - # wger - from wger.trophies.checkers.personal_record import PersonalRecordChecker - log = WorkoutLog(user=self.user, exercise=self.exercise, repetitions=10, weight=100) one_rm = round(100 * (36.0 / (37 - 10)), 2) @@ -577,10 +563,16 @@ class PersonalRecordCheckerTestCase(WgerTestCase): self.assertEqual(estimate, one_rm) self.assertEqual(checker.get_context_data().get('one_rep_max_estimate'), one_rm) - def test_estimate_1rm_raises_on_missing_values(self): - # wger - from wger.trophies.checkers.personal_record import PersonalRecordChecker + def test_estimate_1rm_with_rir(self): + log = WorkoutLog(user=self.user, exercise=self.exercise, repetitions=10, weight=100, rir=2) + one_rm = round(100 * (36.0 / (37 - 12)), 2) + checker = PersonalRecordChecker(self.user, self.trophy, {'log': log}) + estimate = checker._estimate_one_rep_max() + self.assertEqual(estimate, one_rm) + self.assertEqual(checker.get_context_data().get('one_rep_max_estimate'), one_rm) + + def test_estimate_1rm_raises_on_missing_values(self): # No log checker = PersonalRecordChecker(self.user, self.trophy, {}) with self.assertRaises(ValueError): @@ -602,9 +594,6 @@ class PersonalRecordCheckerTestCase(WgerTestCase): self.assertIsNone(context.get('one_rep_max_estimate')) def test_estimate_1rm_raises_on_repetitions_37(self): - # wger - from wger.trophies.checkers.personal_record import PersonalRecordChecker - log = WorkoutLog(user=self.user, exercise=self.exercise, weight=100, repetitions=37) checker = PersonalRecordChecker(self.user, self.trophy, {'log': log}) with self.assertRaises(ValueError): diff --git a/wger/trophies/tests/test_overview.py b/wger/trophies/tests/test_overview.py index 5d57d08b9..f8dbab7b4 100644 --- a/wger/trophies/tests/test_overview.py +++ b/wger/trophies/tests/test_overview.py @@ -18,6 +18,7 @@ import logging # wger from wger.core.tests.base_testcase import WgerAccessTestCase + logger = logging.getLogger(__name__) @@ -25,6 +26,7 @@ class TrophiesOverviewTestCase(WgerAccessTestCase): """ Test case for the trophies overview page """ + url = 'trophies:admin-overview' anonymous_fail = True user_success = 'admin' @@ -41,5 +43,3 @@ class TrophiesOverviewTestCase(WgerAccessTestCase): 'member4', 'member5', ) - - diff --git a/wger/trophies/views.py b/wger/trophies/views.py index a559930fe..09a17779c 100644 --- a/wger/trophies/views.py +++ b/wger/trophies/views.py @@ -25,6 +25,7 @@ from django.views.generic import ListView # wger from wger.trophies.models import Trophy + logger = logging.getLogger(__name__)