Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions extras/docker/production/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@
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
Expand Down
48 changes: 48 additions & 0 deletions wger/exercises/cache.py
Original file line number Diff line number Diff line change
@@ -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'))
19 changes: 3 additions & 16 deletions wger/exercises/management/commands/warmup-exercise-api-cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -48,20 +47,8 @@ def handle(self, **options):

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)
21 changes: 21 additions & 0 deletions wger/exercises/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

# 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,
Expand Down Expand Up @@ -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']:
Expand Down Expand Up @@ -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',
)
60 changes: 60 additions & 0 deletions wger/exercises/tests/test_cache.py
Original file line number Diff line number Diff line change
@@ -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)
2 changes: 2 additions & 0 deletions wger/settings_global.py
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,8 @@
'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,
Expand Down