Rename exercisebase model to exercise

This commit is contained in:
Roland Geider
2024-07-08 19:13:29 +02:00
parent 385df1d64f
commit e7d4ffc216
41 changed files with 36643 additions and 36620 deletions

View File

@@ -64,13 +64,13 @@ filter_dump(('exercises.exercisecategory',), 'categories.json')
filter_dump(('exercises.exerciseimage',), 'exercise-images.json')
filter_dump(
(
'exercises.exercisebase',
'exercises.exercise',
'exercises.variation',
),
'exercise-base-data.json',
)
filter_dump(
('exercises.exercise', 'exercises.exercisecomment', 'exercises.alias'), 'translations.json'
('exercises.translation', 'exercises.exercisecomment', 'exercises.alias'), 'translations.json'
)
filter_dump(
(

View File

@@ -108,6 +108,21 @@
"exercises",
"translation"
],
[
"add_exercise",
"exercises",
"exercise"
],
[
"change_exercise",
"exercises",
"exercise"
],
[
"delete_exercise",
"exercises",
"exercise"
],
[
"add_exercisecategory",
"exercises",
@@ -497,6 +512,21 @@
"exercises",
"translation"
],
[
"add_exercise",
"exercises",
"exercise"
],
[
"change_exercise",
"exercises",
"exercise"
],
[
"delete_exercise",
"exercises",
"exercise"
],
[
"add_exercisecategory",
"exercises",

View File

@@ -24,13 +24,13 @@ from wger.exercises.models import (
Alias,
DeletionLog,
Equipment,
Translation,
ExerciseBase,
Exercise,
ExerciseCategory,
ExerciseComment,
ExerciseImage,
ExerciseVideo,
Muscle,
Translation,
Variation,
)
from wger.utils.cache import CacheKeyMapper
@@ -42,7 +42,7 @@ class ExerciseBaseSerializer(serializers.ModelSerializer):
"""
class Meta:
model = ExerciseBase
model = Exercise
fields = [
'id',
'uuid',
@@ -307,7 +307,7 @@ class ExerciseTranslationBaseInfoSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(required=False, read_only=True)
uuid = serializers.UUIDField(required=False, read_only=True)
exercise_base = serializers.PrimaryKeyRelatedField(
queryset=ExerciseBase.objects.all(),
queryset=Exercise.objects.all(),
required=True,
)
aliases = ExerciseInfoAliasSerializer(source='alias_set', many=True, read_only=True)
@@ -344,7 +344,7 @@ class ExerciseTranslationSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(required=False, read_only=True)
uuid = serializers.UUIDField(required=False, read_only=True)
exercise_base = serializers.PrimaryKeyRelatedField(
queryset=ExerciseBase.objects.all(),
queryset=Exercise.objects.all(),
required=True,
)
@@ -369,7 +369,7 @@ class ExerciseTranslationSerializer(serializers.ModelSerializer):
# Editing an existing object
# -> Check if the language already exists, excluding the current object
if self.instance:
if self.instance.exercise_base.exercises.filter(
if self.instance.exercise_base.translations.filter(
~Q(id=self.instance.pk),
language=value['language'],
).exists():
@@ -450,7 +450,7 @@ class ExerciseBaseInfoSerializer(serializers.ModelSerializer):
last_update_global = serializers.DateTimeField(read_only=True)
class Meta:
model = ExerciseBase
model = Exercise
depth = 1
fields = [
'id',

View File

@@ -71,13 +71,13 @@ from wger.exercises.models import (
Alias,
DeletionLog,
Equipment,
Translation,
ExerciseBase,
Exercise,
ExerciseCategory,
ExerciseComment,
ExerciseImage,
ExerciseVideo,
Muscle,
Translation,
Variation,
)
from wger.exercises.views.helper import StreamVerbs
@@ -101,7 +101,7 @@ class ExerciseBaseViewSet(ModelViewSet):
For a read-only endpoint with all the information of an exercise, see /api/v2/exercisebaseinfo/
"""
queryset = ExerciseBase.translations.all()
queryset = Exercise.with_translations.all()
serializer_class = ExerciseBaseSerializer
permission_classes = (CanContributeExercises,)
ordering_fields = '__all__'
@@ -134,7 +134,7 @@ class ExerciseBaseViewSet(ModelViewSet):
action_object=serializer.instance,
)
def perform_destroy(self, instance: ExerciseBase):
def perform_destroy(self, instance: Exercise):
"""Manually delete the exercise and set the replacement, if any"""
uuid = self.request.query_params.get('replaced_by', '')
@@ -430,7 +430,7 @@ class ExerciseBaseInfoViewset(viewsets.ReadOnlyModelViewSet):
is the recommended way to access the exercise data.
"""
queryset = ExerciseBase.objects.all()
queryset = Exercise.objects.all()
serializer_class = ExerciseBaseInfoSerializer
ordering_fields = '__all__'
filterset_fields = (

View File

@@ -27,7 +27,7 @@ class ExerciseConfig(AppConfig):
registry.register(self.get_model('Alias'))
registry.register(self.get_model('Translation'))
registry.register(self.get_model('ExerciseBase'))
registry.register(self.get_model('Exercise'))
registry.register(self.get_model('ExerciseComment'))
registry.register(self.get_model('ExerciseImage'))
registry.register(self.get_model('ExerciseVideo'))

File diff suppressed because it is too large Load Diff

View File

@@ -25,7 +25,7 @@
},
{
"pk": 1,
"model": "exercises.exercisebase",
"model": "exercises.exercise",
"fields": {
"uuid": "acad3949-36fb-4481-9a72-be2ddae2bc05",
"category": 2,
@@ -43,7 +43,7 @@
},
{
"pk": 2,
"model": "exercises.exercisebase",
"model": "exercises.exercise",
"fields": {
"uuid": "ae3328ba-9a35-4731-bc23-5da50720c5aa",
"category": 2,
@@ -61,7 +61,7 @@
},
{
"pk": 3,
"model": "exercises.exercisebase",
"model": "exercises.exercise",
"fields": {
"uuid": "c2dc11e9-b14a-49cb-9d11-d1b9441f16f4",
"category": 3,
@@ -75,7 +75,7 @@
},
{
"pk": 4,
"model": "exercises.exercisebase",
"model": "exercises.exercise",
"fields": {
"uuid": "a2f5b6ef-b780-49c0-8d96-fdaff23e27ce",
"category": 3,
@@ -90,7 +90,7 @@
},
{
"pk": 5,
"model": "exercises.exercisebase",
"model": "exercises.exercise",
"fields": {
"uuid": "1ae6a28d-10e7-4ecf-af4f-905f8193e2c6",
"category": 1,
@@ -102,7 +102,7 @@
},
{
"pk": 6,
"model": "exercises.exercisebase",
"model": "exercises.exercise",
"fields": {
"uuid": "95a7e546-e8f8-4521-a76b-983d94161b25",
"category": 1,
@@ -114,7 +114,7 @@
},
{
"pk": 7,
"model": "exercises.exercisebase",
"model": "exercises.exercise",
"fields": {
"uuid": "b186f1f8-4957-44dc-bf30-d0b00064ce6f",
"category": 1,
@@ -126,7 +126,7 @@
},
{
"pk": 8,
"model": "exercises.exercisebase",
"model": "exercises.exercise",
"fields": {
"uuid": "c2078aac-e4e2-4103-a845-6252a3eb795e",
"category": 1,

File diff suppressed because one or more lines are too long

View File

@@ -16,8 +16,10 @@
from django.core.management.base import BaseCommand
# wger
from wger.exercises.models import ExerciseBase
from wger.exercises.models import Translation
from wger.exercises.models import (
Exercise,
Translation,
)
class Command(BaseCommand):
@@ -61,8 +63,8 @@ class Command(BaseCommand):
if exercise_base_id is not None:
try:
exercise_base = ExerciseBase.objects.get(id=exercise_base_id)
except ExerciseBase.DoesNotExist:
exercise_base = Exercise.objects.get(id=exercise_base_id)
except Exercise.DoesNotExist:
self.print_error('Failed to find exercise base')
return
exercise_base.license_author = author_name
@@ -71,7 +73,7 @@ class Command(BaseCommand):
if exercise_id is not None:
try:
exercise = Translation.objects.get(id=exercise_id)
except ExerciseBase.DoesNotExist:
except Exercise.DoesNotExist:
self.print_error('Failed to find exercise')
return
exercise.license_author = author_name

View File

@@ -21,8 +21,8 @@ from django.core.management.base import BaseCommand
# wger
from wger.core.models import Language
from wger.exercises.models import (
Exercise,
Translation,
ExerciseBase,
)
@@ -39,7 +39,7 @@ class Command(BaseCommand):
out = []
languages = Language.objects.all()
for base in ExerciseBase.objects.all():
for base in Exercise.objects.all():
data = {
'base': {
'uuid': base.uuid,

View File

@@ -21,7 +21,7 @@ from django.core.management.base import BaseCommand
# wger
from wger.core.models import Language
from wger.exercises.models import ExerciseBase
from wger.exercises.models import Exercise
from wger.utils.constants import ENGLISH_SHORT_NAME
@@ -88,16 +88,16 @@ class Command(BaseCommand):
self.english = Language.objects.get(short_name=ENGLISH_SHORT_NAME)
for base in ExerciseBase.objects.all():
for base in Exercise.objects.all():
self.handle_untranslated(base, delete_untranslated)
self.handle_no_english(base, delete_no_english)
self.handle_duplicate_translations(base, delete_duplicates)
def handle_untranslated(self, base: ExerciseBase, delete: bool):
def handle_untranslated(self, base: Exercise, delete: bool):
"""
Delete exercises without translations
"""
if not base.pk or base.exercises.count():
if not base.pk or base.translations.count():
return
self.stdout.write(self.style.WARNING(f'Exercise {base.uuid} has no translations!'))
@@ -105,8 +105,8 @@ class Command(BaseCommand):
base.delete()
self.stdout.write(' -> deleted')
def handle_no_english(self, base: ExerciseBase, delete: bool):
if not base.pk or base.exercises.filter(language=self.english).exists():
def handle_no_english(self, base: Exercise, delete: bool):
if not base.pk or base.translations.filter(language=self.english).exists():
return
self.stdout.write(self.style.WARNING(f'Exercise {base.uuid} has no English translation!'))
@@ -114,11 +114,11 @@ class Command(BaseCommand):
base.delete()
self.stdout.write(' -> deleted')
def handle_duplicate_translations(self, base: ExerciseBase, delete: bool):
def handle_duplicate_translations(self, base: Exercise, delete: bool):
if not base.pk:
return
exercise_languages = base.exercises.values_list('language', flat=True)
exercise_languages = base.translations.values_list('language', flat=True)
duplicates = [
Language.objects.get(pk=item)
for item, count in collections.Counter(exercise_languages).items()
@@ -133,7 +133,7 @@ class Command(BaseCommand):
# Output the duplicates
for language in duplicates:
translations = base.exercises.filter(language=language)
translations = base.translations.filter(language=language)
self.stdout.write(f'language {language.short_name}:')
for translation in translations:
self.stdout.write(f' * {translation.name} {translation.uuid}')

View File

@@ -29,10 +29,10 @@ from wger.core.models import (
from wger.exercises.models import (
Alias,
Equipment,
Translation,
ExerciseBase,
Exercise,
ExerciseCategory,
ExerciseVideo,
Translation,
Variation,
)
from wger.utils.constants import CC_BY_SA_4_ID
@@ -120,12 +120,12 @@ class Command(BaseCommand):
continue
base = (
ExerciseBase.objects.get_or_create(
Exercise.objects.get_or_create(
uuid=base_uuid,
defaults={'category': ExerciseCategory.objects.get(name=base_category)},
)[0]
if not new_base
else ExerciseBase()
else Exercise()
)
# Update the base data
@@ -273,9 +273,9 @@ class Command(BaseCommand):
if base_uuid:
try:
ExerciseBase.objects.filter(uuid=base_uuid).delete()
Exercise.objects.filter(uuid=base_uuid).delete()
self.stdout.write(f'* Deleted base {base_uuid}')
except ExerciseBase.DoesNotExist:
except Exercise.DoesNotExist:
pass
if translation_uuid:

View File

@@ -17,7 +17,7 @@ from django.core.management.base import BaseCommand
# wger
from wger.exercises.api.serializers import ExerciseBaseInfoSerializer
from wger.exercises.models import ExerciseBase
from wger.exercises.models import Exercise
from wger.utils.cache import reset_exercise_api_cache
@@ -47,14 +47,14 @@ class Command(BaseCommand):
force = options['force']
if exercise_base_id:
exercise = ExerciseBase.objects.get(pk=exercise_base_id)
exercise = Exercise.objects.get(pk=exercise_base_id)
self.handle_cache(exercise, force)
return
for exercise in ExerciseBase.translations.all():
for exercise in Exercise.with_translations.all():
self.handle_cache(exercise, force)
def handle_cache(self, exercise: ExerciseBase, force: bool):
def handle_cache(self, exercise: Exercise, force: bool):
if force:
self.stdout.write(f'Force updating cache for exercise base {exercise.uuid}')
else:

View File

@@ -23,14 +23,14 @@ class ExerciseBaseManagerTranslations(models.Manager):
"""Returns all exercise bases that have at least one translation"""
def get_queryset(self):
return super().get_queryset().annotate(count=Count('exercises')).filter(count__gt=0)
return super().get_queryset().annotate(count=Count('translations')).filter(count__gt=0)
class ExerciseBaseManagerNoTranslations(models.Manager):
"""Returns all exercise bases that have no translations"""
def get_queryset(self):
return super().get_queryset().annotate(count=Count('exercises')).filter(count=0)
return super().get_queryset().annotate(count=Count('translations')).filter(count=0)
class ExerciseBaseManagerAll(models.Manager):

View File

@@ -1,4 +1,4 @@
# Generated by Django 4.2.13 on 2024-06-12 12:59
# Generated by Django 4.2.13 on 2024-07-08 17:02
from django.db import migrations
@@ -6,6 +6,7 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('exercises', '0030_increase_author_field_length'),
('manager', '0017_alter_workoutlog_exercise_base'),
]
operations = [
@@ -17,4 +18,12 @@ class Migration(migrations.Migration):
old_name='HistoricalExercise',
new_name='HistoricalTranslation',
),
migrations.RenameModel(
old_name='ExerciseBase',
new_name='Exercise',
),
migrations.RenameModel(
old_name='HistoricalExerciseBase',
new_name='HistoricalExercise',
),
]

View File

@@ -15,7 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# Local
from .base import ExerciseBase
from .base import Exercise
from .category import ExerciseCategory
from .comment import ExerciseComment
from .deletion_log import DeletionLog

View File

@@ -24,7 +24,10 @@ from typing import (
# Django
from django.core.checks import Warning
from django.db import models, OperationalError
from django.db import (
OperationalError,
models,
)
from django.db.models import Q
from django.urls import reverse
from django.utils.translation import (
@@ -55,14 +58,14 @@ from .muscle import Muscle
from .variation import Variation
class ExerciseBase(AbstractLicenseModel, AbstractHistoryMixin, models.Model):
class Exercise(AbstractLicenseModel, AbstractHistoryMixin, models.Model):
"""
Model for an exercise base
"""
objects = ExerciseBaseManagerAll()
no_translations = ExerciseBaseManagerNoTranslations()
translations = ExerciseBaseManagerTranslations()
with_translations = ExerciseBaseManagerTranslations()
"""
Custom Query Manager
"""
@@ -150,8 +153,8 @@ class ExerciseBase(AbstractLicenseModel, AbstractHistoryMixin, models.Model):
Warning(
'exercises without translations',
hint=f'There are {no_translations} exercises without translations, this will '
'cause problems! You can output or delete them with "python manage.py '
'exercises-health-check --help"',
'cause problems! You can output or delete them with "python manage.py '
'exercises-health-check --help"',
id='wger.W002',
)
)
@@ -172,7 +175,7 @@ class ExerciseBase(AbstractLicenseModel, AbstractHistoryMixin, models.Model):
All authors history related to the BaseExercise.
"""
collect_for_models = [
*self.exercises.all(),
*self.translations.all(),
*self.exercisevideo_set.all(),
*self.exerciseimage_set.all(),
]
@@ -187,7 +190,7 @@ class ExerciseBase(AbstractLicenseModel, AbstractHistoryMixin, models.Model):
self.last_update,
*[image.last_update for image in self.exerciseimage_set.all()],
*[video.last_update for video in self.exercisevideo_set.all()],
*[translation.last_update for translation in self.exercises.all()],
*[translation.last_update for translation in self.translations.all()],
datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone.utc),
)
@@ -203,7 +206,7 @@ class ExerciseBase(AbstractLicenseModel, AbstractHistoryMixin, models.Model):
"""
Returns the languages from the exercises that use this base
"""
return [exercise.language for exercise in self.exercises.all()]
return [exercise.language for exercise in self.translations.all()]
@property
def base_variations(self):
@@ -212,7 +215,7 @@ class ExerciseBase(AbstractLicenseModel, AbstractHistoryMixin, models.Model):
"""
if not self.variations:
return []
return self.variations.exercisebase_set.filter(~Q(id=self.id))
return self.variations.exercise_set.filter(~Q(id=self.id))
def get_translation(self, language: Optional[str] = None):
"""
@@ -230,14 +233,14 @@ class ExerciseBase(AbstractLicenseModel, AbstractHistoryMixin, models.Model):
language = language or get_language()
try:
translation = self.exercises.get(language__short_name=language)
translation = self.translations.get(language__short_name=language)
except Translation.DoesNotExist:
try:
translation = self.exercises.get(language__short_name=ENGLISH_SHORT_NAME)
translation = self.translations.get(language__short_name=ENGLISH_SHORT_NAME)
except Translation.DoesNotExist:
translation = self.exercises.first()
translation = self.translations.first()
except Translation.MultipleObjectsReturned:
translation = self.exercises.filter(language__short_name=language).first()
translation = self.translations.filter(language__short_name=language).first()
return translation
@@ -255,12 +258,12 @@ class ExerciseBase(AbstractLicenseModel, AbstractHistoryMixin, models.Model):
if replace_by:
try:
ExerciseBase.objects.get(uuid=replace_by)
except ExerciseBase.DoesNotExist:
Exercise.objects.get(uuid=replace_by)
except Exercise.DoesNotExist:
replace_by = None
log = DeletionLog(
model_type=DeletionLog.MODEL_BASE,
model_type=DeletionLog.MODEL_EXERCISE,
uuid=self.uuid,
comment=f'Exercise base of {self.get_translation(ENGLISH_SHORT_NAME)}',
replaced_by=replace_by,

View File

@@ -30,13 +30,13 @@ class DeletionLog(models.Model):
different DBs, without any way of cleaning them up.
"""
MODEL_BASE = 'base'
MODEL_EXERCISE = 'base'
MODEL_TRANSLATION = 'translation'
MODEL_IMAGE = 'image'
MODEL_VIDEO = 'video'
MODELS = [
(MODEL_BASE, 'base'),
(MODEL_EXERCISE, 'base'),
(MODEL_TRANSLATION, 'translation'),
(MODEL_IMAGE, 'image'),
(MODEL_VIDEO, 'video'),

View File

@@ -21,12 +21,11 @@ import uuid
# Django
from django.db import models
from django.utils.translation import gettext_lazy as _
# Third Party
from simple_history.models import HistoricalRecords
# wger
from wger.exercises.models import ExerciseBase
from wger.exercises.models import Exercise
from wger.utils.cache import reset_exercise_api_cache
from wger.utils.helpers import BaseImage
from wger.utils.models import (
@@ -70,7 +69,7 @@ class ExerciseImage(AbstractLicenseModel, AbstractHistoryMixin, models.Model, Ba
"""Globally unique ID, to identify the image across installations"""
exercise_base = models.ForeignKey(
ExerciseBase,
Exercise,
verbose_name=_('Exercise'),
on_delete=models.CASCADE,
)
@@ -185,7 +184,7 @@ class ExerciseImage(AbstractLicenseModel, AbstractHistoryMixin, models.Model, Ba
@classmethod
def from_json(
cls,
connect_to: ExerciseBase,
connect_to: Exercise,
retrieved_image,
json_data: dict,
generate_uuid: bool = False,

View File

@@ -30,7 +30,7 @@ from simple_history.models import HistoricalRecords
# wger
from wger.core.models import Language
from wger.exercises.models import ExerciseBase
from wger.exercises.models import Exercise
from wger.utils.cache import reset_exercise_api_cache
from wger.utils.models import (
AbstractHistoryMixin,
@@ -84,12 +84,12 @@ class Translation(AbstractLicenseModel, AbstractHistoryMixin, models.Model):
"""Globally unique ID, to identify the exercise across installations"""
exercise_base = models.ForeignKey(
ExerciseBase,
Exercise,
verbose_name='ExerciseBase',
on_delete=models.CASCADE,
default=None,
null=False,
related_name='exercises',
related_name='translations',
)
""" Refers to the base exercise with non translated information """
@@ -176,8 +176,8 @@ class Translation(AbstractLicenseModel, AbstractHistoryMixin, models.Model):
"""
out = []
if self.exercise_base.variations:
for variation in self.exercise_base.variations.exercisebase_set.all():
for exercise in variation.exercises.filter(language=self.language).all():
for variation in self.exercise_base.variations.exercise_set.all():
for exercise in variation.translations.filter(language=self.language).all():
out.append(exercise)
return out

View File

@@ -22,14 +22,12 @@ import uuid
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import gettext_lazy as _
# Third Party
from simple_history.models import HistoricalRecords
# wger
from wger.utils.cache import reset_exercise_api_cache
try:
# Third Party
import ffmpeg
@@ -37,7 +35,7 @@ except ImportError:
ffmpeg = None
# wger
from wger.exercises.models import ExerciseBase
from wger.exercises.models import Exercise
from wger.utils.models import (
AbstractHistoryMixin,
AbstractLicenseModel,
@@ -94,7 +92,7 @@ class ExerciseVideo(AbstractLicenseModel, AbstractHistoryMixin, models.Model):
"""Globally unique ID, to identify the image across installations"""
exercise_base = models.ForeignKey(
ExerciseBase,
Exercise,
verbose_name=_('Exercise'),
on_delete=models.CASCADE,
)

View File

@@ -32,9 +32,9 @@ from easy_thumbnails.signals import saved_file
# wger
from wger.exercises.models import (
DeletionLog,
Translation,
ExerciseImage,
ExerciseVideo,
Translation,
)
@@ -113,27 +113,24 @@ def delete_exercise_video_on_update(sender, instance: ExerciseVideo, **kwargs):
@receiver(pre_delete, sender=Translation)
def add_deletion_log_translation(sender, instance: Translation, **kwargs):
log = DeletionLog(
DeletionLog(
model_type=DeletionLog.MODEL_TRANSLATION,
uuid=instance.uuid,
comment=instance.name,
)
log.save()
).save()
@receiver(pre_delete, sender=ExerciseImage)
def add_deletion_log_image(sender, instance: ExerciseImage, **kwargs):
log = DeletionLog(
DeletionLog(
model_type=DeletionLog.MODEL_IMAGE,
uuid=instance.uuid,
)
log.save()
).save()
@receiver(pre_delete, sender=ExerciseVideo)
def add_deletion_log_video(sender, instance: ExerciseVideo, **kwargs):
log = DeletionLog(
DeletionLog(
model_type=DeletionLog.MODEL_VIDEO,
uuid=instance.uuid,
)
log.save()
).save()

View File

@@ -44,13 +44,13 @@ from wger.exercises.models import (
Alias,
DeletionLog,
Equipment,
Translation,
ExerciseBase,
Exercise,
ExerciseCategory,
ExerciseComment,
ExerciseImage,
ExerciseVideo,
Muscle,
Translation,
)
from wger.manager.models import (
SlotConfig,
@@ -83,7 +83,7 @@ def sync_exercises(
muscles = [Muscle.objects.get(pk=i['id']) for i in data['muscles']]
muscles_sec = [Muscle.objects.get(pk=i['id']) for i in data['muscles_secondary']]
base, base_created = ExerciseBase.objects.update_or_create(
base, base_created = Exercise.objects.update_or_create(
uuid=uuid,
defaults={'category_id': category_id, 'created': created},
)
@@ -295,6 +295,7 @@ def handle_deleted_entries(
style_fn=lambda x: x,
):
if not print_fn:
def print_fn(_):
return None
@@ -308,18 +309,18 @@ def handle_deleted_entries(
replaced_by_uuid = data['replaced_by']
model_type = data['model_type']
if model_type == DeletionLog.MODEL_BASE:
if model_type == DeletionLog.MODEL_EXERCISE:
obj_replaced = None
nr_settings = None
nr_slot_configs = None
nr_logs = None
try:
obj_replaced = ExerciseBase.objects.get(uuid=replaced_by_uuid)
except ExerciseBase.DoesNotExist:
obj_replaced = Exercise.objects.get(uuid=replaced_by_uuid)
except Exercise.DoesNotExist:
pass
try:
obj = ExerciseBase.objects.get(uuid=uuid)
obj = Exercise.objects.get(uuid=uuid)
# Replace exercise in workouts and logs
if obj_replaced:
@@ -337,7 +338,7 @@ def handle_deleted_entries(
print_fn(f'- replaced in {nr_slot_configs} routines with {replaced_by_uuid}')
if nr_logs:
print_fn(f'- replaced in {nr_logs} workout logs with {replaced_by_uuid}')
except ExerciseBase.DoesNotExist:
except Exercise.DoesNotExist:
pass
elif model_type == DeletionLog.MODEL_TRANSLATION:
@@ -391,8 +392,8 @@ def download_exercise_images(
print_fn(f'Processing image {image_uuid}')
try:
exercise = ExerciseBase.objects.get(uuid=image_data['exercise_base_uuid'])
except ExerciseBase.DoesNotExist:
exercise = Exercise.objects.get(uuid=image_data['exercise_base_uuid'])
except Exercise.DoesNotExist:
print_fn(' Remote exercise base not found in local DB, skipping...')
continue
@@ -423,8 +424,8 @@ def download_exercise_videos(
print_fn(f'Processing video {video_uuid}')
try:
exercise = ExerciseBase.objects.get(uuid=video_data['exercise_base_uuid'])
except ExerciseBase.DoesNotExist:
exercise = Exercise.objects.get(uuid=video_data['exercise_base_uuid'])
except Exercise.DoesNotExist:
print_fn(' Remote exercise base not found in local DB, skipping...')
continue

View File

@@ -21,7 +21,7 @@ from django.core.management import call_command
# wger
from wger.core.tests.base_testcase import WgerTestCase
from wger.exercises.models import Translation
from wger.exercises.models.base import ExerciseBase
from wger.exercises.models.base import Exercise
class ChangeExerciseAuthorTestCase(WgerTestCase):
@@ -52,14 +52,14 @@ class ChangeExerciseAuthorTestCase(WgerTestCase):
"""
Test to ensure command can handle an exercise base id passed
"""
exercise = ExerciseBase.objects.get(id=2)
exercise = Exercise.objects.get(id=2)
self.assertNotEqual(exercise.license_author, 'tom')
args = ['--author-name', 'tom', '--exercise-base-id', '2']
call_command('change-exercise-author', *args, stdout=self.out, no_color=True)
self.assertIn('Exercise and/or exercise base has been updated', self.out.getvalue())
exercise = ExerciseBase.objects.get(id=2)
exercise = Exercise.objects.get(id=2)
self.assertEqual(exercise.license_author, 'tom')
def test_can_update_exercise(self):

View File

@@ -19,8 +19,8 @@ from uuid import UUID
from wger.core.tests.base_testcase import WgerTestCase
from wger.exercises.models import (
DeletionLog,
Exercise,
Translation,
ExerciseBase,
)
@@ -29,25 +29,25 @@ class DeletionLogTestCase(WgerTestCase):
Test that the deletion log entries are correctly generated
"""
def test_base(self):
def test_exercise(self):
"""
Test that an entry is generated when a base is deleted
"""
self.assertEqual(DeletionLog.objects.all().count(), 0)
base = ExerciseBase.objects.get(pk=1)
base.delete()
exercise = Exercise.objects.get(pk=1)
exercise.delete()
# Base is deleted
count_base_logs = DeletionLog.objects.filter(
model_type=DeletionLog.MODEL_BASE,
uuid=base.uuid,
# Exercise is deleted
count_exercise_logs = DeletionLog.objects.filter(
model_type=DeletionLog.MODEL_EXERCISE,
uuid=exercise.uuid,
).count()
log = DeletionLog.objects.get(pk=1)
self.assertEqual(count_base_logs, 1)
self.assertEqual(log.model_type, 'base')
self.assertEqual(log.uuid, base.uuid)
self.assertEqual(count_exercise_logs, 1)
self.assertEqual(log.model_type, DeletionLog.MODEL_EXERCISE)
self.assertEqual(log.uuid, exercise.uuid)
self.assertEqual(log.comment, 'Exercise base of An exercise')
self.assertEqual(log.replaced_by, None)
@@ -57,49 +57,49 @@ class DeletionLogTestCase(WgerTestCase):
# First translation
log2 = DeletionLog.objects.get(pk=4)
self.assertEqual(log2.model_type, 'translation')
self.assertEqual(log2.model_type, DeletionLog.MODEL_TRANSLATION)
self.assertEqual(log2.uuid, UUID('9838235c-e38f-4ca6-921e-9d237d8e0813'))
self.assertEqual(log2.comment, 'An exercise')
self.assertEqual(log2.replaced_by, None)
# Second translation
log3 = DeletionLog.objects.get(pk=5)
self.assertEqual(log3.model_type, 'translation')
self.assertEqual(log3.model_type, DeletionLog.MODEL_TRANSLATION)
self.assertEqual(log3.uuid, UUID('13b532f9-d208-462e-a000-7b9982b2b53e'))
self.assertEqual(log3.comment, 'Test exercise 123')
self.assertEqual(log3.replaced_by, None)
def test_base_with_replaced_by(self):
def test_exercise_with_replaced_by(self):
"""
Test that an entry is generated when a base is deleted and the replaced by is
set correctly
"""
self.assertEqual(DeletionLog.objects.all().count(), 0)
exercise = ExerciseBase.objects.get(pk=1)
exercise = Exercise.objects.get(pk=1)
exercise.delete(replace_by='ae3328ba-9a35-4731-bc23-5da50720c5aa')
# Base is deleted
log = DeletionLog.objects.get(pk=1)
self.assertEqual(log.model_type, 'base')
self.assertEqual(log.model_type, DeletionLog.MODEL_EXERCISE)
self.assertEqual(log.uuid, exercise.uuid)
self.assertEqual(log.replaced_by, UUID('ae3328ba-9a35-4731-bc23-5da50720c5aa'))
def test_base_with_nonexistent_replaced_by(self):
def test_exercise_with_nonexistent_replaced_by(self):
"""
Test that an entry is generated when a base is deleted and the replaced by is
set correctly. If the UUID is not found in the DB, it's set to None
"""
self.assertEqual(DeletionLog.objects.all().count(), 0)
exercise = ExerciseBase.objects.get(pk=1)
exercise = Exercise.objects.get(pk=1)
exercise.delete(replace_by='12345678-1234-1234-1234-1234567890ab')
# Base is deleted
log = DeletionLog.objects.get(pk=1)
self.assertEqual(log.model_type, 'base')
self.assertEqual(log.model_type, DeletionLog.MODEL_EXERCISE)
self.assertEqual(log.replaced_by, None)
def test_translation(self):
@@ -113,6 +113,7 @@ class DeletionLogTestCase(WgerTestCase):
# Translation is deleted
count = DeletionLog.objects.filter(
model_type=DeletionLog.MODEL_TRANSLATION, uuid=translation.uuid
model_type=DeletionLog.MODEL_TRANSLATION,
uuid=translation.uuid,
).count()
self.assertEqual(count, 1)

View File

@@ -19,9 +19,9 @@ from django.core.cache import cache
from wger.core.tests.base_testcase import WgerTestCase
from wger.exercises.models import (
Alias,
Translation,
ExerciseBase,
Exercise,
ExerciseComment,
Translation,
)
from wger.utils.cache import cache_mapper
@@ -45,7 +45,7 @@ class ExerciseApiCacheTestCase(WgerTestCase):
self.client.get(self.url)
self.assertTrue(cache.get(self.cache_key))
exercise = ExerciseBase.objects.get(pk=1)
exercise = Exercise.objects.get(pk=1)
exercise.category_id = 1
exercise.save()
@@ -59,7 +59,7 @@ class ExerciseApiCacheTestCase(WgerTestCase):
self.client.get(self.url)
self.assertTrue(cache.get(self.cache_key))
exercise = ExerciseBase.objects.get(pk=1)
exercise = Exercise.objects.get(pk=1)
exercise.delete()
self.assertFalse(cache.get(self.cache_key))

View File

@@ -23,8 +23,8 @@ from wger.core.tests.api_base_test import ExerciseCrudApiTestCase
from wger.core.tests.base_testcase import WgerTestCase
from wger.exercises.models import (
DeletionLog,
Exercise,
Translation,
ExerciseBase,
)
from wger.utils.constants import CC_BY_SA_4_ID
@@ -57,7 +57,7 @@ class ExerciseBaseTestCase(WgerTestCase):
"""
Test that the base correctly returns translated exercises
"""
exercise = ExerciseBase.objects.get(pk=1).get_translation('de')
exercise = Exercise.objects.get(pk=1).get_translation('de')
self.assertEqual(exercise.name, 'An exercise')
def test_language_utils_no_translation_exists(self):
@@ -65,7 +65,7 @@ class ExerciseBaseTestCase(WgerTestCase):
Test that the base correctly returns the English translation if the
requested language does not exist
"""
exercise = ExerciseBase.objects.get(pk=1).get_translation('fr')
exercise = Exercise.objects.get(pk=1).get_translation('fr')
self.assertEqual(exercise.name, 'Test exercise 123')
def test_language_utils_no_translation_fallback(self):
@@ -73,7 +73,7 @@ class ExerciseBaseTestCase(WgerTestCase):
Test that the base correctly returns the first translation if for whatever
reason English is not available
"""
exercise = ExerciseBase.objects.get(pk=2).get_translation('pt')
exercise = Exercise.objects.get(pk=2).get_translation('pt')
self.assertEqual(exercise.name, 'Very cool exercise')
@@ -82,10 +82,10 @@ class ExerciseBaseTestCase(WgerTestCase):
# Even if these exercises have the same base, only the variations for
# their respective languages are returned.
exercise = ExerciseBase.objects.get(pk=1)
exercise = Exercise.objects.get(pk=1)
self.assertListEqual(sorted([i.id for i in exercise.base_variations]), [2])
exercise2 = ExerciseBase.objects.get(pk=3)
exercise2 = Exercise.objects.get(pk=3)
self.assertEqual(sorted([i.id for i in exercise2.base_variations]), [4])
def test_images(self):
@@ -131,14 +131,14 @@ class ExerciseCustomApiTestCase(ExerciseCrudApiTestCase):
Test that it is not possible to change the license of an existing
exercise base
"""
exercise = ExerciseBase.objects.get(pk=self.pk)
exercise = Exercise.objects.get(pk=self.pk)
self.assertEqual(exercise.license_id, 2)
self.authenticate('trainer1')
response = self.client.patch(self.url_detail, data={'license': 3})
self.assertEqual(response.status_code, status.HTTP_200_OK)
exercise = ExerciseBase.objects.get(pk=self.pk)
exercise = Exercise.objects.get(pk=self.pk)
self.assertEqual(exercise.license_id, 2)
def test_cant_set_license(self):
@@ -152,5 +152,5 @@ class ExerciseCustomApiTestCase(ExerciseCrudApiTestCase):
response = self.client.post(self.url, data=self.data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
exercise = ExerciseBase.objects.get(pk=self.pk)
exercise = Exercise.objects.get(pk=self.pk)
self.assertEqual(exercise.license_id, CC_BY_SA_4_ID)

View File

@@ -18,8 +18,8 @@ import datetime
# wger
from wger.core.tests.base_testcase import WgerTestCase
from wger.exercises.models import (
Exercise,
Translation,
ExerciseBase,
)
@@ -34,12 +34,12 @@ class ExerciseBaseTranslationHandlingTestCase(WgerTestCase):
Translation.objects.get(pk=5).delete()
def test_managers(self):
self.assertEqual(ExerciseBase.translations.all().count(), 7)
self.assertEqual(ExerciseBase.no_translations.all().count(), 1)
self.assertEqual(ExerciseBase.objects.all().count(), 8)
self.assertEqual(Exercise.with_translations.all().count(), 7)
self.assertEqual(Exercise.no_translations.all().count(), 1)
self.assertEqual(Exercise.objects.all().count(), 8)
def test_checks(self):
out = ExerciseBase.check()
out = Exercise.check()
self.assertEqual(len(out), 1)
self.assertEqual(out[0].id, 'wger.W002')
@@ -49,11 +49,11 @@ class ExerciseBaseModelTestCase(WgerTestCase):
Test custom model logic
"""
exercise: ExerciseBase
exercise: Exercise
def setUp(self):
super().setUp()
self.exercise = ExerciseBase.objects.get(pk=1)
self.exercise = Exercise.objects.get(pk=1)
def test_access_date(self):
utc = datetime.timezone.utc
@@ -66,7 +66,7 @@ class ExerciseBaseModelTestCase(WgerTestCase):
)
self.assertEqual(
max(*[translation.last_update for translation in self.exercise.exercises.all()]),
max(*[translation.last_update for translation in self.exercise.translations.all()]),
datetime.datetime(2022, 2, 2, 5, 45, 11, tzinfo=utc),
)

View File

@@ -17,12 +17,10 @@ from django.core.files import File
# wger
from wger.core.tests import api_base_test
from wger.core.tests.base_testcase import (
WgerTestCase,
)
from wger.core.tests.base_testcase import WgerTestCase
from wger.exercises.models import (
Translation,
ExerciseImage,
Translation,
)

View File

@@ -27,12 +27,10 @@ from rest_framework import status
# wger
from wger.core.tests import api_base_test
from wger.core.tests.api_base_test import ExerciseCrudApiTestCase
from wger.core.tests.base_testcase import (
WgerTestCase,
)
from wger.core.tests.base_testcase import WgerTestCase
from wger.exercises.models import (
Translation,
Muscle,
Translation,
)
from wger.utils.constants import CC_BY_SA_4_ID

View File

@@ -22,8 +22,8 @@ from django.test import SimpleTestCase
# wger
from wger.core.tests.base_testcase import WgerTestCase
from wger.exercises.models import (
Exercise,
Translation,
ExerciseBase,
)
@@ -93,7 +93,7 @@ class TestHealthCheckManagementCommands(WgerTestCase):
call_command('exercises-health-check', '--delete-untranslated', stdout=self.out)
self.assertIn('-> deleted', self.out.getvalue())
self.assertRaises(ExerciseBase.DoesNotExist, ExerciseBase.objects.get, pk=1)
self.assertRaises(Exercise.DoesNotExist, Exercise.objects.get, pk=1)
def test_find_no_english_translation(self):
Translation.objects.get(pk=1).delete()
@@ -110,7 +110,7 @@ class TestHealthCheckManagementCommands(WgerTestCase):
call_command('exercises-health-check', '--delete-no-english', stdout=self.out)
self.assertIn('-> deleted', self.out.getvalue())
self.assertRaises(ExerciseBase.DoesNotExist, ExerciseBase.objects.get, pk=1)
self.assertRaises(Exercise.DoesNotExist, Exercise.objects.get, pk=1)
def test_find_duplicate_translations(self):
exercise = Translation.objects.get(pk=1)

View File

@@ -23,10 +23,10 @@ from wger.core.models import (
from wger.core.tests.base_testcase import WgerTestCase
from wger.exercises.models import (
Equipment,
Translation,
ExerciseBase,
Exercise,
ExerciseCategory,
Muscle,
Translation,
)
from wger.exercises.sync import (
handle_deleted_entries,
@@ -647,11 +647,11 @@ class TestSyncMethods(WgerTestCase):
@patch('requests.get', return_value=MockDeletionLogResponse())
def test_deletion_log(self, mock_request):
self.assertEqual(ExerciseBase.objects.count(), 8)
self.assertEqual(Exercise.objects.count(), 8)
self.assertEqual(Translation.objects.count(), 11)
exercise1 = ExerciseBase.objects.get(pk=1)
exercise2 = ExerciseBase.objects.get(pk=4)
exercise1 = Exercise.objects.get(pk=1)
exercise2 = Exercise.objects.get(pk=4)
self.assertFalse(SlotConfig.objects.filter(exercise=exercise2).count())
self.assertFalse(WorkoutLog.objects.filter(exercise=exercise2).count())
@@ -664,10 +664,10 @@ class TestSyncMethods(WgerTestCase):
'https://wger.de/api/v2/deletion-log/?limit=100',
headers=wger_headers(),
)
self.assertEqual(ExerciseBase.objects.count(), 7)
self.assertEqual(Exercise.objects.count(), 7)
self.assertEqual(Translation.objects.count(), 8)
self.assertRaises(Translation.DoesNotExist, Translation.objects.get, pk=3)
self.assertRaises(ExerciseBase.DoesNotExist, ExerciseBase.objects.get, pk=1)
self.assertRaises(Exercise.DoesNotExist, Exercise.objects.get, pk=1)
# Workouts and logs have been moved
for pk in slot_configs:
@@ -677,9 +677,9 @@ class TestSyncMethods(WgerTestCase):
@patch('requests.get', return_value=MockExerciseResponse())
def test_exercise_sync(self, mock_request):
self.assertEqual(ExerciseBase.objects.count(), 8)
self.assertEqual(Exercise.objects.count(), 8)
self.assertEqual(Translation.objects.count(), 11)
exercise = ExerciseBase.objects.get(uuid='ae3328ba-9a35-4731-bc23-5da50720c5aa')
exercise = Exercise.objects.get(uuid='ae3328ba-9a35-4731-bc23-5da50720c5aa')
self.assertEqual(exercise.category_id, 2)
sync_exercises(lambda x: x)
@@ -688,11 +688,11 @@ class TestSyncMethods(WgerTestCase):
'https://wger.de/api/v2/exercisebaseinfo/?limit=100',
headers=wger_headers(),
)
self.assertEqual(ExerciseBase.objects.count(), 9)
self.assertEqual(Exercise.objects.count(), 9)
self.assertEqual(Translation.objects.count(), 14)
# New exercise was created
new_exercise = ExerciseBase.objects.get(uuid='1b020b3a-3732-4c7e-92fd-a0cec90ed69b')
new_exercise = Exercise.objects.get(uuid='1b020b3a-3732-4c7e-92fd-a0cec90ed69b')
self.assertEqual(new_exercise.category_id, 2)
self.assertEqual([e.id for e in new_exercise.equipment.all()], [2])
self.assertEqual([m.id for m in new_exercise.muscles.all()], [2])
@@ -714,7 +714,7 @@ class TestSyncMethods(WgerTestCase):
self.assertEqual(translation_en.description, 'TBD')
# Existing exercise was updated
exercise = ExerciseBase.objects.get(uuid='ae3328ba-9a35-4731-bc23-5da50720c5aa')
exercise = Exercise.objects.get(uuid='ae3328ba-9a35-4731-bc23-5da50720c5aa')
self.assertEqual(exercise.category_id, 3)
translation_de = exercise.get_translation('de')

View File

@@ -31,9 +31,7 @@ from django.utils.translation import (
gettext as _,
gettext_lazy,
)
from django.views.generic import (
DeleteView,
)
from django.views.generic import DeleteView
# wger
from wger.exercises.models import Translation

View File

@@ -27,7 +27,7 @@ from rest_framework.decorators import action
from rest_framework.response import Response
# wger
from wger.exercises.models import ExerciseBase
from wger.exercises.models import Exercise
from wger.manager.api.consts import CONFIG_FIELDS
from wger.manager.api.serializers import (
DaySerializer,
@@ -228,7 +228,7 @@ class WorkoutViewSet(viewsets.ModelViewSet):
if not base_id:
return Response("Please provide an base ID in the 'id' GET parameter")
base = get_object_or_404(ExerciseBase, pk=base_id)
base = get_object_or_404(Exercise, pk=base_id)
logs = base.workoutlog_set.filter(
user=self.request.user,
weight_unit__in=(1, 2),

View File

@@ -17,6 +17,14 @@
This file contains forms used in the application
"""
# Third Party
from crispy_forms.helper import FormHelper
from crispy_forms.layout import (
Column,
Layout,
Row,
Submit,
)
# Django
from django.forms import (
BooleanField,
@@ -35,21 +43,12 @@ from django.utils.translation import (
gettext_lazy,
)
# Third Party
from crispy_forms.helper import FormHelper
from crispy_forms.layout import (
Column,
Layout,
Row,
Submit,
)
# wger
from wger.core.models import (
RepetitionUnit,
WeightUnit,
)
from wger.exercises.models import ExerciseBase
from wger.exercises.models import Exercise
from wger.manager.consts import RIR_OPTIONS
from wger.manager.models import (
Workout,
@@ -119,7 +118,7 @@ class WorkoutLogForm(ModelForm):
required=False,
)
exercise_base = ModelChoiceField(
queryset=ExerciseBase.objects.all(),
queryset=Exercise.objects.all(),
label=_('Exercise'),
required=False,
)

View File

@@ -23,7 +23,7 @@ from django.contrib.auth.models import User
from django.core.management.base import BaseCommand
# wger
from wger.exercises.models import ExerciseBase
from wger.exercises.models import Exercise
from wger.manager.models import (
Day,
RepsConfig,
@@ -34,7 +34,6 @@ from wger.manager.models import (
WeightConfig,
)
logger = logging.getLogger(__name__)
@@ -111,7 +110,7 @@ class Command(BaseCommand):
day_list[-1].save()
# Add exercises (no supersets, all very simple)
exercise_list = [i for i in ExerciseBase.objects.all()]
exercise_list = [i for i in Exercise.objects.all()]
for day in day_list:
nr_of_exercises = random.randint(3, 6)
exercises = random.choices(exercise_list, k=nr_of_exercises)

View File

@@ -11,7 +11,7 @@ class Migration(migrations.Migration):
dependencies = [
('core', '0016_alter_language_short_name'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('exercises', '0028_add_uuid_alias_and_comments'),
('exercises', '0032_rename_exercise'),
('manager', '0017_alter_workoutlog_exercise_base'),
]
@@ -149,7 +149,7 @@ class Migration(migrations.Migration):
(
'exercise',
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to='exercises.exercisebase'
on_delete=django.db.models.deletion.CASCADE, to='exercises.exercise'
),
),
(

View File

@@ -28,7 +28,7 @@ from wger.core.models import (
RepetitionUnit,
WeightUnit,
)
from wger.exercises.models import ExerciseBase
from wger.exercises.models import Exercise
from wger.manager.consts import RIR_OPTIONS
from wger.manager.models.session import WorkoutSession
from wger.utils.cache import reset_workout_log
@@ -77,7 +77,7 @@ class WorkoutLog(models.Model):
"""
exercise = models.ForeignKey(
ExerciseBase,
Exercise,
verbose_name=_('Exercise'),
on_delete=models.CASCADE,
)

View File

@@ -27,7 +27,7 @@ from wger.core.models import (
RepetitionUnit,
WeightUnit,
)
from wger.exercises.models import ExerciseBase
from wger.exercises.models import Exercise
from wger.manager.dataclasses import SetConfigData
from wger.manager.models.abstract_config import (
AbstractChangeConfig,
@@ -59,7 +59,7 @@ class SlotConfig(models.Model):
)
exercise = models.ForeignKey(
ExerciseBase,
Exercise,
on_delete=models.CASCADE,
)

View File

@@ -17,7 +17,6 @@ import datetime
import logging
# Django
from django.contrib.auth.models import User
from django.core.cache import cache
from django.urls import (
reverse,
@@ -30,15 +29,9 @@ from wger.core.tests.base_testcase import (
WgerDeleteTestCase,
WgerTestCase,
)
from wger.exercises.models import ExerciseBase
from wger.manager.models import (
Workout,
WorkoutLog,
WorkoutSession,
)
from wger.manager.models import WorkoutLog
from wger.utils.cache import cache_mapper
logger = logging.getLogger(__name__)

View File

@@ -15,24 +15,22 @@
# Standard Library
import logging
# Third Party
import requests
# Django
from django.conf import settings
from django.contrib.auth.models import User
from django.core.cache import cache
from django.shortcuts import render
# Third Party
import requests
# wger
from wger.core.forms import (
RegistrationForm,
RegistrationFormNoCaptcha,
)
from wger.exercises.models import ExerciseBase
from wger.exercises.models import Exercise
from wger.nutrition.models import Ingredient
logger = logging.getLogger(__name__)
CACHE_KEY = 'landing-page-context'
@@ -48,7 +46,7 @@ def features(request):
result_github_api = requests.get('https://api.github.com/repos/wger-project/wger').json()
context = {
'nr_users': User.objects.count(),
'nr_exercises': ExerciseBase.objects.count(),
'nr_exercises': Exercise.objects.count(),
'nr_ingredients': Ingredient.objects.count(),
'nr_stars': result_github_api.get('stargazers_count', '2000'),
}