Merge pull request #1977 from wbonicki/feature/1694

Feature/1694
This commit is contained in:
Roland Geider
2025-09-30 13:00:09 +02:00
committed by GitHub
6 changed files with 136 additions and 16 deletions

View File

@@ -101,6 +101,8 @@ WGER_SETTINGS["SYNC_INGREDIENTS_CELERY"] = env.bool("SYNC_INGREDIENTS_CELERY", F
WGER_SETTINGS["SYNC_OFF_DAILY_DELTA_CELERY"] = env.bool("SYNC_OFF_DAILY_DELTA_CELERY", False)
WGER_SETTINGS["USE_RECAPTCHA"] = env.bool("USE_RECAPTCHA", False)
WGER_SETTINGS["USE_CELERY"] = env.bool("USE_CELERY", False)
WGER_SETTINGS["CACHE_API_EXERCISES_CELERY"] = env.bool("CACHE_API_EXERCISES_CELERY", False)
WGER_SETTINGS["CACHE_API_EXERCISES_CELERY_FORCE_UPDATE"] = env.bool("CACHE_API_EXERCISES_CELERY_FORCE_UPDATE", False)
#
# Auth Proxy Authentication

48
wger/exercises/cache.py Normal file
View File

@@ -0,0 +1,48 @@
# 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 typing import Callable
# wger
from wger.exercises.api.serializers import ExerciseInfoSerializer
from wger.exercises.models import Exercise
from wger.utils.cache import reset_exercise_api_cache
def cache_exercise(
exercise: Exercise, force=False, print_fn: Callable = print, style_fn: Callable = lambda x: x
):
"""
Caches a provided exercise.
"""
if force:
print_fn(f'Force updating cache for exercise {exercise.uuid}')
reset_exercise_api_cache(exercise.uuid)
else:
print_fn(f'Warming cache for exercise {exercise.uuid}')
serializer = ExerciseInfoSerializer(exercise)
serializer.data
def cache_api_exercises(
print_fn: Callable,
force: bool,
style_fn: Callable = lambda x: x,
):
print_fn('*** Caching API exercises ***')
for exercise in Exercise.with_translations.all():
cache_exercise(exercise, force, print_fn, style_fn)
print_fn(style_fn('Exercises cached!\n'))

View File

@@ -16,9 +16,8 @@
from django.core.management.base import BaseCommand
# wger
from wger.exercises.api.serializers import ExerciseInfoSerializer
from wger.exercises.cache import cache_exercise
from wger.exercises.models import Exercise
from wger.utils.cache import reset_exercise_api_cache
class Command(BaseCommand):
@@ -48,20 +47,8 @@ class Command(BaseCommand):
if exercise_id:
exercise = Exercise.objects.get(pk=exercise_id)
self.handle_cache(exercise, force)
cache_exercise(exercise, force, self.stdout.write)
return
for exercise in Exercise.with_translations.all():
self.handle_cache(exercise, force)
def handle_cache(self, exercise: Exercise, force: bool):
if force:
self.stdout.write(f'Force updating cache for exercise base {exercise.uuid}')
else:
self.stdout.write(f'Warming cache for exercise base {exercise.uuid}')
if force:
reset_exercise_api_cache(exercise.uuid)
serializer = ExerciseInfoSerializer(exercise)
serializer.data
cache_exercise(exercise, force, self.stdout.write)

View File

@@ -24,6 +24,7 @@ from celery.schedules import crontab
# wger
from wger.celery_configuration import app
from wger.exercises.cache import cache_api_exercises
from wger.exercises.sync import (
download_exercise_images,
download_exercise_videos,
@@ -70,6 +71,15 @@ def sync_videos_task():
download_exercise_videos(logger.info)
@app.task
def cache_api_exercises_task():
"""
Fetches all exercises from database and caches them.
"""
force = settings.WGER_SETTINGS['CACHE_API_EXERCISES_CELERY_FORCE_UPDATE']
cache_api_exercises(logger.info, force)
@app.on_after_finalize.connect
def setup_periodic_tasks(sender, **kwargs):
if settings.WGER_SETTINGS['SYNC_EXERCISES_CELERY']:
@@ -104,3 +114,14 @@ def setup_periodic_tasks(sender, **kwargs):
sync_videos_task.s(),
name='Sync exercise videos',
)
if settings.WGER_SETTINGS['CACHE_API_EXERCISES_CELERY']:
sender.add_periodic_task(
crontab(
hour=str(random.randint(0, 23)),
minute=str(random.randint(0, 59)),
day_of_week=str(random.randint(0, 6)),
),
cache_api_exercises_task.s(),
name='Cache API exercises',
)

View File

@@ -0,0 +1,60 @@
# Standard Library
from unittest.mock import (
MagicMock,
patch,
)
# wger
from wger.core.tests.base_testcase import WgerTestCase
from wger.exercises.cache import (
cache_api_exercises,
cache_exercise,
)
from wger.exercises.models import Exercise
class TestCacheExercise(WgerTestCase):
@patch('wger.exercises.cache.reset_exercise_api_cache')
@patch('wger.exercises.cache.ExerciseInfoSerializer')
def test_cache_exercise_force_true(self, mock_serializer, mock_reset_cache):
exercise = Exercise.objects.first()
output = []
serializer_instance = MagicMock()
serializer_instance.data = {'mocked': True}
mock_serializer.return_value = serializer_instance
cache_exercise(exercise, force=True, print_fn=output.append)
mock_reset_cache.assert_called_once_with(exercise.uuid)
mock_serializer.assert_called_once_with(exercise)
self.assertTrue(any('Force updating cache' in msg for msg in output))
@patch('wger.exercises.cache.reset_exercise_api_cache')
@patch('wger.exercises.cache.ExerciseInfoSerializer')
def test_cache_exercise_force_false(self, mock_serializer, mock_reset_cache):
exercise = Exercise.objects.first()
output = []
serializer_instance = MagicMock()
serializer_instance.data = {'mocked': True}
mock_serializer.return_value = serializer_instance
cache_exercise(exercise, force=False, print_fn=output.append)
mock_reset_cache.assert_not_called()
mock_serializer.assert_called_once_with(exercise)
self.assertTrue(any('Warming cache' in msg for msg in output))
@patch('wger.exercises.cache.cache_exercise')
def test_cache_api_exercises_calls_all(self, mock_cache_exercise):
output = []
called_exercises = []
def fake_cache(exercise, force, print_fn, style_fn):
called_exercises.append(exercise)
mock_cache_exercise.side_effect = fake_cache
cache_api_exercises(print_fn=output.append, force=True)
self.assertTrue(len(called_exercises) > 0)

View File

@@ -556,6 +556,8 @@ WGER_SETTINGS = {
'SYNC_EXERCISE_VIDEOS_CELERY': False,
'SYNC_INGREDIENTS_CELERY': False,
'SYNC_OFF_DAILY_DELTA_CELERY': False,
'CACHE_API_EXERCISES_CELERY': False,
'CACHE_API_EXERCISES_CELERY_FORCE_UPDATE': False,
'TWITTER': False,
'MASTODON': 'https://fosstodon.org/@wger',
'USE_CELERY': False,