From fbabb5169f66d853d2fc72d4de6246a517c5f20c Mon Sep 17 00:00:00 2001 From: Yiga-fred Date: Tue, 7 Aug 2018 19:55:59 +0300 Subject: [PATCH] [Feature #159236196] Cache nutritional plan values --- wger/nutrition/__init__.py | 1 + wger/nutrition/apps.py | 25 +++++ wger/nutrition/models.py | 102 ++++++++++-------- wger/nutrition/signals.py | 21 ++++ .../test_nutrional_plan_values_canonical.py | 54 ++++++++++ wger/utils/cache.py | 8 ++ 6 files changed, 165 insertions(+), 46 deletions(-) create mode 100644 wger/nutrition/apps.py create mode 100644 wger/nutrition/signals.py create mode 100644 wger/nutrition/tests/test_nutrional_plan_values_canonical.py diff --git a/wger/nutrition/__init__.py b/wger/nutrition/__init__.py index a915bb26..012ccf7a 100644 --- a/wger/nutrition/__init__.py +++ b/wger/nutrition/__init__.py @@ -19,3 +19,4 @@ from wger import get_version VERSION = get_version() +default_app_config = 'wger.nutrition.apps.NutritionPlanValuesConfig' diff --git a/wger/nutrition/apps.py b/wger/nutrition/apps.py new file mode 100644 index 00000000..ce47c8ed --- /dev/null +++ b/wger/nutrition/apps.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- + +# 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 + +from django.apps import AppConfig + + +class NutritionPlanValuesConfig(AppConfig): + name = 'wger.nutrition' + verbose_name = "NutritionPlan" + + def ready(self): + import wger.nutrition.signals diff --git a/wger/nutrition/models.py b/wger/nutrition/models.py index 45e4731b..e5b075a0 100644 --- a/wger/nutrition/models.py +++ b/wger/nutrition/models.py @@ -110,52 +110,62 @@ def get_nutritional_values(self): ''' Sums the nutritional info of all items in the plan ''' - use_metric = self.user.userprofile.use_metric - unit = 'kg' if use_metric else 'lb' - result = {'total': {'energy': 0, - 'protein': 0, - 'carbohydrates': 0, - 'carbohydrates_sugar': 0, - 'fat': 0, - 'fat_saturated': 0, - 'fibres': 0, - 'sodium': 0}, - 'percent': {'protein': 0, - 'carbohydrates': 0, - 'fat': 0}, - 'per_kg': {'protein': 0, - 'carbohydrates': 0, - 'fat': 0}, - } - - # Energy - for meal in self.meal_set.select_related(): - values = meal.get_nutritional_values(use_metric=use_metric) - for key in result['total'].keys(): - result['total'][key] += values[key] - - energy = result['total']['energy'] - - # In percent - if energy: - for key in result['percent'].keys(): - result['percent'][key] = \ - result['total'][key] * \ - ENERGY_FACTOR[key][unit] / energy * 100 - - # Per body weight - weight_entry = self.get_closest_weight_entry() - if weight_entry: - for key in result['per_kg'].keys(): - result['per_kg'][key] = result['total'][key] / \ - weight_entry.weight - - # Only 2 decimal places, anything else doesn't make sense - for key in result.keys(): - for i in result[key]: - result[key][i] = Decimal(result[key][i]).quantize(TWOPLACES) - - return result + # query for the cache + nutritional_plan_values_canonical_form = cache.get( + cache_mapper.get_nutritional_plan_canonical(self.pk) + ) + # check if cache exit + if not nutritional_plan_values_canonical_form: + + use_metric = self.user.userprofile.use_metric + unit = 'kg' if use_metric else 'lb' + result = {'total': {'energy': 0, + 'protein': 0, + 'carbohydrates': 0, + 'carbohydrates_sugar': 0, + 'fat': 0, + 'fat_saturated': 0, + 'fibres': 0, + 'sodium': 0}, + 'percent': {'protein': 0, + 'carbohydrates': 0, + 'fat': 0}, + 'per_kg': {'protein': 0, + 'carbohydrates': 0, + 'fat': 0}, + } + + # Energy + for meal in self.meal_set.select_related(): + values = meal.get_nutritional_values(use_metric=use_metric) + for key in result['total'].keys(): + result['total'][key] += values[key] + + energy = result['total']['energy'] + + # In percent + if energy: + for key in result['percent'].keys(): + result['percent'][key] = \ + result['total'][key] * \ + ENERGY_FACTOR[key][unit] / energy * 100 + + # Per body weight + weight_entry = self.get_closest_weight_entry() + if weight_entry: + for key in result['per_kg'].keys(): + result['per_kg'][key] = result['total'][key] / \ + weight_entry.weight + + # Only 2 decimal places, anything else doesn't make sense + for key in result.keys(): + for i in result[key]: + result[key][i] = Decimal(result[key][i]).quantize(TWOPLACES) + nutritional_plan_values_canonical_form = result + cache.set(cache_mapper.get_nutritional_plan_canonical(self.pk), + nutritional_plan_values_canonical_form + ) + return nutritional_plan_values_canonical_form def get_closest_weight_entry(self): ''' diff --git a/wger/nutrition/signals.py b/wger/nutrition/signals.py new file mode 100644 index 00000000..54032d1a --- /dev/null +++ b/wger/nutrition/signals.py @@ -0,0 +1,21 @@ +from django.db.models.signals import post_delete, post_save +from django.dispatch import receiver +from wger.nutrition.models import NutritionPlan, Meal, MealItem +from django.core.cache import cache +from wger.utils.cache import cache_mapper + + +@receiver(post_save, sender=NutritionPlan) +@receiver(post_delete, sender=NutritionPlan) +@receiver(post_delete, sender=MealItem) +@receiver(post_save, sender=MealItem) +@receiver(post_save, sender=Meal) +@receiver(post_delete, sender=Meal) +def reset_nutrional_plan_canonical(sender, **kwargs): + ''' + Function to send signal + ''' + instance_sender = kwargs['instance'] + if isinstance(instance_sender, (MealItem, NutritionPlan, Meal)): + cache.delete(cache_mapper.get_nutritional_plan_canonical( + instance_sender.get_owner_object().id)) diff --git a/wger/nutrition/tests/test_nutrional_plan_values_canonical.py b/wger/nutrition/tests/test_nutrional_plan_values_canonical.py new file mode 100644 index 00000000..0184615e --- /dev/null +++ b/wger/nutrition/tests/test_nutrional_plan_values_canonical.py @@ -0,0 +1,54 @@ +# 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 +from django.core.cache import cache + +from wger.nutrition.models import NutritionPlan, Meal, MealItem +from wger.utils.cache import cache_mapper +from wger.core.tests.base_testcase import WorkoutManagerTestCase + + +class NutritionPlanCacheTestCase(WorkoutManagerTestCase): + ''' + Test case for the NutritionalPlan canonical representation + ''' + def test_nutritional_plan_canonical_form_cache(self): + ''' + Test that the nuttritional plan cache is correctly generated + ''' + self.assertFalse(cache.get(cache_mapper.get_nutritional_plan_canonical(1))) + nutritional_plan = NutritionPlan.objects.get(pk=1) + nutritional_plan.get_nutritional_values() + self.assertTrue(cache.get(cache_mapper.get_nutritional_plan_canonical(1))) + + def test_nutritional_plan_canonical_form_cache_save(self): + ''' + Tests the nutritional_plan values cache when saving a nutritional plan + ''' + nutritional_plan = NutritionPlan.objects.get(pk=1) + nutritional_plan.get_nutritional_values() + self.assertTrue(cache.get(cache_mapper.get_nutritional_plan_canonical(1))) + + nutritional_plan.save() + self.assertFalse(cache.get(cache_mapper.get_nutritional_plan_canonical(1))) + + def test_nutritional_plan_canonical_form_cache_delete(self): + ''' + Tests the nutrional plan values cache when deleting + ''' + nutritional_plan = NutritionPlan.objects.get(pk=1) + nutritional_plan.get_nutritional_values() + self.assertTrue(cache.get(cache_mapper.get_nutritional_plan_canonical(1))) + + nutritional_plan.delete() + self.assertFalse(cache.get(cache_mapper.get_workout_canonical(1))) diff --git a/wger/utils/cache.py b/wger/utils/cache.py index 9efcaeb3..e56f0b16 100644 --- a/wger/utils/cache.py +++ b/wger/utils/cache.py @@ -66,6 +66,8 @@ class CacheKeyMapper(object): EXERCISE_CACHE_KEY_MUSCLE_BG = 'exercise-muscle-bg-{0}' INGREDIENT_CACHE_KEY = 'ingredient-{0}' WORKOUT_CANONICAL_REPRESENTATION = 'workout-canonical-representation-{0}' + NUTRITION_PLAN_VALUES_CANONICAL_REPRESENTATION = 'nutritional-plan-values-canonical-\ + representation-{0}' WORKOUT_LOG_LIST = 'workout-log-hash-{0}' def get_pk(self, param): @@ -115,5 +117,11 @@ def get_workout_log_list(self, hash_value): ''' return self.WORKOUT_LOG_LIST.format(hash_value) + def get_nutritional_plan_canonical(self, param): + ''' + Return the nutritional plan canonical representation + ''' + return self.NUTRITION_PLAN_VALUES_CANONICAL_REPRESENTATION.format(self.get_pk(param)) + cache_mapper = CacheKeyMapper()