diff --git a/wger/exercises/management/commands/warmup-exercise-api-cache.py b/wger/exercises/management/commands/warmup-exercise-api-cache.py index 29db2a1f5..3b5a4b1fd 100644 --- a/wger/exercises/management/commands/warmup-exercise-api-cache.py +++ b/wger/exercises/management/commands/warmup-exercise-api-cache.py @@ -13,13 +13,12 @@ # You should have received a copy of the GNU Affero General Public License # Django -from django.core.cache import cache from django.core.management.base import BaseCommand # wger from wger.exercises.api.serializers import ExerciseBaseInfoSerializer from wger.exercises.models import ExerciseBase -from wger.utils.cache import CacheKeyMapper +from wger.utils.cache import reset_exercise_api_cache class Command(BaseCommand): @@ -62,7 +61,7 @@ def handle_cache(self, exercise: ExerciseBase, force: bool): self.stdout.write(f"Warming cache for exercise base {exercise.uuid}") if force: - cache.delete(CacheKeyMapper.get_exercise_api_key(exercise.uuid)) + reset_exercise_api_cache(exercise.uuid) serializer = ExerciseBaseInfoSerializer(exercise) serializer.data diff --git a/wger/exercises/models/base.py b/wger/exercises/models/base.py index 756060a68..308273963 100644 --- a/wger/exercises/models/base.py +++ b/wger/exercises/models/base.py @@ -42,6 +42,7 @@ ExerciseBaseManagerNoTranslations, ExerciseBaseManagerTranslations, ) +from wger.utils.cache import reset_exercise_api_cache from wger.utils.constants import ENGLISH_SHORT_NAME from wger.utils.models import ( AbstractHistoryMixin, @@ -236,6 +237,11 @@ def get_translation(self, language: Optional[str] = None): return translation + def save(self, *args, **kwargs): + super().save(*args, **kwargs) + + reset_exercise_api_cache(self.uuid) + def delete(self, using=None, keep_parents=False, replace_by: str = None): """ Save entry to log @@ -257,4 +263,6 @@ def delete(self, using=None, keep_parents=False, replace_by: str = None): ) log.save() + reset_exercise_api_cache(self.uuid) + return super().delete(using, keep_parents) diff --git a/wger/exercises/models/comment.py b/wger/exercises/models/comment.py index b3749e309..f71f2583e 100644 --- a/wger/exercises/models/comment.py +++ b/wger/exercises/models/comment.py @@ -25,7 +25,10 @@ from simple_history.models import HistoricalRecords # wger -from wger.utils.cache import reset_workout_canonical_form +from wger.utils.cache import ( + reset_exercise_api_cache, + reset_workout_canonical_form, +) # Local from .exercise import Exercise @@ -71,7 +74,10 @@ def save(self, *args, **kwargs): for setting in self.exercise.exercise_base.setting_set.all(): reset_workout_canonical_form(setting.set.exerciseday.training_id) - super(ExerciseComment, self).save(*args, **kwargs) + # Api cache + reset_exercise_api_cache(self.exercise.exercise_base.uuid) + + super().save(*args, **kwargs) def delete(self, *args, **kwargs): """ @@ -80,7 +86,10 @@ def delete(self, *args, **kwargs): for setting in self.exercise.exercise_base.setting_set.all(): reset_workout_canonical_form(setting.set.exerciseday.training.pk) - super(ExerciseComment, self).delete(*args, **kwargs) + # Api cache + reset_exercise_api_cache(self.exercise.exercise_base.uuid) + + super().delete(*args, **kwargs) def get_owner_object(self): """ diff --git a/wger/exercises/models/exercise.py b/wger/exercises/models/exercise.py index 047b9bc03..0dbee8375 100644 --- a/wger/exercises/models/exercise.py +++ b/wger/exercises/models/exercise.py @@ -31,7 +31,10 @@ # wger from wger.core.models import Language from wger.exercises.models import ExerciseBase -from wger.utils.cache import reset_workout_canonical_form +from wger.utils.cache import ( + reset_exercise_api_cache, + reset_workout_canonical_form, +) from wger.utils.models import ( AbstractHistoryMixin, AbstractLicenseModel, @@ -119,7 +122,10 @@ def save(self, *args, **kwargs): """ Reset all cached infos """ - super(Exercise, self).save(*args, **kwargs) + super().save(*args, **kwargs) + + # Api cache + reset_exercise_api_cache(self.exercise_base.uuid) # Cached workouts for setting in self.exercise_base.setting_set.all(): @@ -133,7 +139,10 @@ def delete(self, *args, **kwargs): for setting in self.exercise_base.setting_set.all(): reset_workout_canonical_form(setting.set.exerciseday.training.pk) - super(Exercise, self).delete(*args, **kwargs) + # Api cache + reset_exercise_api_cache(self.exercise_base.uuid) + + super().delete(*args, **kwargs) def __str__(self): """ diff --git a/wger/exercises/models/exercise_alias.py b/wger/exercises/models/exercise_alias.py index 0ccff1940..9ff823a53 100644 --- a/wger/exercises/models/exercise_alias.py +++ b/wger/exercises/models/exercise_alias.py @@ -25,7 +25,10 @@ from simple_history.models import HistoricalRecords # wger -from wger.utils.cache import reset_workout_canonical_form +from wger.utils.cache import ( + reset_exercise_api_cache, + reset_workout_canonical_form, +) # Local from .exercise import Exercise @@ -63,6 +66,15 @@ def __str__(self): """ return self.alias + def save(self, *args, **kwargs): + """ + Reset cached workouts + """ + # Api cache + reset_exercise_api_cache(self.exercise.exercise_base.uuid) + + super().save(*args, **kwargs) + def delete(self, *args, **kwargs): """ Reset cached workouts @@ -70,7 +82,10 @@ def delete(self, *args, **kwargs): for setting in self.exercise.exercise_base.setting_set.all(): reset_workout_canonical_form(setting.set.exerciseday.training.pk) - super(Alias, self).delete(*args, **kwargs) + # Api cache + reset_exercise_api_cache(self.exercise.exercise_base.uuid) + + super().delete(*args, **kwargs) def get_owner_object(self): """ diff --git a/wger/exercises/models/image.py b/wger/exercises/models/image.py index 80956af5e..548fd99d3 100644 --- a/wger/exercises/models/image.py +++ b/wger/exercises/models/image.py @@ -27,6 +27,7 @@ # wger from wger.exercises.models import ExerciseBase +from wger.utils.cache import reset_exercise_api_cache from wger.utils.helpers import BaseImage from wger.utils.models import ( AbstractHistoryMixin, @@ -145,14 +146,17 @@ def save(self, *args, **kwargs): .count(): self.is_main = True + # Api cache + reset_exercise_api_cache(self.exercise_base.uuid) + # And go on - super(ExerciseImage, self).save(*args, **kwargs) + super().save(*args, **kwargs) def delete(self, *args, **kwargs): """ Reset all cached infos """ - super(ExerciseImage, self).delete(*args, **kwargs) + super().delete(*args, **kwargs) # Make sure there is always a main image if not ExerciseImage.objects.all().filter(exercise_base=self.exercise_base, is_main=True diff --git a/wger/exercises/models/video.py b/wger/exercises/models/video.py index 724d76c03..b47f4d444 100644 --- a/wger/exercises/models/video.py +++ b/wger/exercises/models/video.py @@ -26,6 +26,8 @@ # Third Party from simple_history.models import HistoricalRecords +# wger +from wger.utils.cache import reset_exercise_api_cache try: # Third Party @@ -210,4 +212,7 @@ def save(self, *args, **kwargs): self.codec = stream['codec_name'] self.codec_long = stream['codec_long_name'] - super(ExerciseVideo, self).save(*args, **kwargs) + # Api cache + reset_exercise_api_cache(self.exercise_base.uuid) + + super().save(*args, **kwargs) diff --git a/wger/exercises/tests/test_exercise_api_cache.py b/wger/exercises/tests/test_exercise_api_cache.py new file mode 100644 index 000000000..413b3da3c --- /dev/null +++ b/wger/exercises/tests/test_exercise_api_cache.py @@ -0,0 +1,148 @@ +# 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 + +# Django +from django.core.cache import cache + +# wger +from wger.core.tests.base_testcase import ( + WgerTestCase, +) +from wger.exercises.models import ( + Alias, + Exercise, + ExerciseBase, + ExerciseComment, +) +from wger.utils.cache import cache_mapper + + +class ExerciseApiCacheTestCase(WgerTestCase): + """ + Tests the API cache for the exercisebaseinfo endpoint + """ + + exercise_id = 1 + exercise_uuid = 'acad3949-36fb-4481-9a72-be2ddae2bc05' + url = '/api/v2/exercisebaseinfo/1/' + + cache_key = cache_mapper.get_exercise_api_key('acad3949-36fb-4481-9a72-be2ddae2bc05') + + def test_edit_exercise(self): + """ + Tests editing an exercise + """ + self.assertFalse(cache.get(self.cache_key)) + self.client.get(self.url) + self.assertTrue(cache.get(self.cache_key)) + + exercise = ExerciseBase.objects.get(pk=1) + exercise.category_id = 1 + exercise.save() + + self.assertFalse(cache.get(self.cache_key)) + + def test_delete_exercise(self): + """ + Tests deleting an exercise + """ + self.assertFalse(cache.get(self.cache_key)) + self.client.get(self.url) + self.assertTrue(cache.get(self.cache_key)) + + exercise = ExerciseBase.objects.get(pk=1) + exercise.delete() + + self.assertFalse(cache.get(self.cache_key)) + + def test_edit_translation(self): + """ + Tests editing a translation + """ + self.assertFalse(cache.get(self.cache_key)) + self.client.get(self.url) + self.assertTrue(cache.get(self.cache_key)) + + translation = Exercise.objects.get(pk=1) + translation.name = "something else" + translation.save() + + self.assertFalse(cache.get(self.cache_key)) + + def test_delete_translation(self): + """ + Tests deleting a translation + """ + self.assertFalse(cache.get(self.cache_key)) + self.client.get(self.url) + self.assertTrue(cache.get(self.cache_key)) + + translation = Exercise.objects.get(pk=1) + translation.delete() + + self.assertFalse(cache.get(self.cache_key)) + + def test_edit_comment(self): + """ + Tests editing a comment + """ + self.assertFalse(cache.get(self.cache_key)) + self.client.get(self.url) + self.assertTrue(cache.get(self.cache_key)) + + comment = ExerciseComment.objects.get(pk=1) + comment.name = "The Shiba Inu (柴犬) is a breed of hunting dog from Japan" + comment.save() + + self.assertFalse(cache.get(self.cache_key)) + + def test_delete_comment(self): + """ + Tests deleting a comment + """ + self.assertFalse(cache.get(self.cache_key)) + self.client.get(self.url) + self.assertTrue(cache.get(self.cache_key)) + + comment = ExerciseComment.objects.get(pk=1) + comment.delete() + + self.assertFalse(cache.get(self.cache_key)) + + def test_edit_alias(self): + """ + Tests editing an alias + """ + self.assertFalse(cache.get(self.cache_key)) + self.client.get(self.url) + self.assertTrue(cache.get(self.cache_key)) + + alias = Alias.objects.get(pk=1) + alias.name = "Hachikō" + alias.save() + + self.assertFalse(cache.get(self.cache_key)) + + def test_delete_alias(self): + """ + Tests deleting an alias + """ + self.assertFalse(cache.get(self.cache_key)) + self.client.get(self.url) + self.assertTrue(cache.get(self.cache_key)) + + alias = Alias.objects.get(pk=1) + alias.delete() + + self.assertFalse(cache.get(self.cache_key)) diff --git a/wger/exercises/tests/test_exercise_comments.py b/wger/exercises/tests/test_exercise_comments.py index 709cd6f33..5410047f3 100644 --- a/wger/exercises/tests/test_exercise_comments.py +++ b/wger/exercises/tests/test_exercise_comments.py @@ -12,22 +12,10 @@ # # You should have received a copy of the GNU Affero General Public License -# Django -from django.core.cache import cache -from django.urls import reverse - # wger from wger.core.tests.api_base_test import ExerciseCrudApiTestCase -from wger.core.tests.base_testcase import ( - WgerAddTestCase, - WgerEditTestCase, - WgerTestCase, -) -from wger.exercises.models import ( - Exercise, - ExerciseComment, -) -from wger.utils.cache import cache_mapper +from wger.core.tests.base_testcase import WgerTestCase +from wger.exercises.models import ExerciseComment class ExerciseCommentRepresentationTestCase(WgerTestCase): diff --git a/wger/utils/cache.py b/wger/utils/cache.py index b5c4c2f90..a83fabb07 100644 --- a/wger/utils/cache.py +++ b/wger/utils/cache.py @@ -35,6 +35,10 @@ def reset_workout_canonical_form(workout_id): cache.delete(cache_mapper.get_workout_canonical(workout_id)) +def reset_exercise_api_cache(uuid: str): + cache.delete(cache_mapper.get_exercise_api_key(uuid)) + + def reset_workout_log(user_pk, year, month, day=None): """ Resets the cached workout logs