Automatically format the code with yapf and isort

This commit is contained in:
Roland Geider
2021-06-23 18:08:42 +02:00
parent 48b99e508a
commit ea2984f3fc
290 changed files with 9798 additions and 5935 deletions

View File

@@ -15,8 +15,7 @@ in a pull request.
## Questions
Are you just using the software and have a question or improvement?
* Ask it on the [gitter channel](https://gitter.im/wger-project/wger),
* the [discord server](https://discord.gg/rPWFv6W)
* Ask on the [discord server](https://discord.gg/rPWFv6W)
* or just [open an issue](https://github.com/wger-project/wger/issues)
## Issues
@@ -30,8 +29,8 @@ You want to contribute code? Awesome! Here are some tips:
* Make sure the tests are running (``python3 manage.py test``)...
* ... and if you write new code, write new tests
* Lint the code with flake8 (``flake8 --config .github/linters/.flake8 ./wger``)
and isort (``isort``)
* Your code will be automatically linted and formatted with yapf and isort
when it's merged.
* You can expect a response from a maintainer within a week, if you
havent heard anything by then, feel free to ping the thread.

View File

@@ -1,16 +0,0 @@
[flake8]
# H101: Use TODO(NAME)
# W503: line break before binary operator
ignore = H101,W503
max-line-length = 100
# Removed migrations from exclude list since github's superlinter lints them anyway
exclude =
extras,
build,
dist,
yarn,
settings.py,
docs,
urls.py : E501
apps.py : F401

View File

@@ -7,12 +7,11 @@
### Please check that the PR fulfills these requirements
- [ ] Tests for the changes have been added (for bug fixes / features)
- [ ] New python code has been linted with with flake8 (``flake8 --config .github/linters/.flake8 ./wger``)
and isort (``isort``)
- [ ] Added yourself to AUTHORS.rst
### Other questions
* Does this PR introduce a breaking change such as a database migration? (i.e.
what changes might users need to make in their running application due to
this PR?)
what changes might users need to make in their local instance due to this PR?)
* Note that your code will be automatically linted and formatted with yapf and
isort when it's merged

View File

@@ -49,7 +49,6 @@ jobs:
uses: docker://ghcr.io/github/super-linter:slim-v4
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VALIDATE_PYTHON_FLAKE8: true
VALIDATE_MD: true
VALIDATE_JAVASCRIPT_ES: true
VALIDATE_JSON: true

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# flake8: noqa
#
# wger Workout Manager documentation build configuration file, created by
# sphinx-quickstart on Mon Dec 1 22:22:02 2014.

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# flake8: noqa
"""
Simple FunkLoad test
"""

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# flake8: noqa
# This file is part of wger Workout Manager.
#

View File

@@ -9,7 +9,7 @@ from django.core.management import execute_from_command_line
# wger
from wger.tasks import (
get_path,
setup_django_environment
setup_django_environment,
)

View File

@@ -67,3 +67,17 @@ combine_as_imports=True
# If set to true - isort will add imports even if the file specified is
# currently completely empty.
force_adds=False
# Include a trailing comma after the imports. This ensures that yapf doesn't
# reformat the code
include_trailing_comma = True
[yapf]
based_on_style = pep8
column_limit = 100
blank_line_before_nested_class_or_def = True
dedent_closing_brackets = True
split_penalty_import_names = 100
## Too much...
#split_all_comma_separated_values = True

View File

@@ -10,7 +10,7 @@
# Third Party
from setuptools import (
find_packages,
setup
setup,
)
# wger

View File

@@ -20,8 +20,6 @@ import sys
# Third Party
from invoke import run
"""
This simple wrapper script is used as a console entry point in the packaged
version of the application. It simply redirects all arguments to the invoke

View File

@@ -15,10 +15,8 @@
# You should have received a copy of the GNU Affero General Public License
# along with Workout Manager. If not, see <http://www.gnu.org/licenses/>.
# wger
from wger import get_version
VERSION = get_version()
default_app_config = 'wger.config.apps.ConfigConfig'

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# flake8: noqa
from django.db import models, migrations
@@ -14,27 +14,68 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='GymConfig',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('default_gym', models.ForeignKey(blank=True, to='gym.Gym',
help_text='Select the default gym for this installation. This will assign all new registered users to this gym and update all existing users without a gym.',
null=True, verbose_name='Default gym', on_delete=models.CASCADE)),
(
'id',
models.AutoField(
verbose_name='ID', serialize=False, auto_created=True, primary_key=True
)
),
(
'default_gym',
models.ForeignKey(
blank=True,
to='gym.Gym',
help_text=
'Select the default gym for this installation. This will assign all new registered users to this gym and update all existing users without a gym.',
null=True,
verbose_name='Default gym',
on_delete=models.CASCADE
)
),
],
options={
},
bases=(models.Model,),
options={},
bases=(models.Model, ),
),
migrations.CreateModel(
name='LanguageConfig',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('item', models.CharField(max_length=2, editable=False, choices=[(b'1', 'Exercises'), (b'2', 'Ingredients')])),
(
'id',
models.AutoField(
verbose_name='ID', serialize=False, auto_created=True, primary_key=True
)
),
(
'item',
models.CharField(
max_length=2,
editable=False,
choices=[(b'1', 'Exercises'), (b'2', 'Ingredients')]
)
),
('show', models.BooleanField(default=1)),
('language', models.ForeignKey(related_name='language_source', editable=False, to='core.Language', on_delete=models.CASCADE)),
('language_target', models.ForeignKey(related_name='language_target', editable=False, to='core.Language', on_delete=models.CASCADE)),
(
'language',
models.ForeignKey(
related_name='language_source',
editable=False,
to='core.Language',
on_delete=models.CASCADE
)
),
(
'language_target',
models.ForeignKey(
related_name='language_target',
editable=False,
to='core.Language',
on_delete=models.CASCADE
)
),
],
options={
'ordering': ['item', 'language_target'],
},
bases=(models.Model,),
bases=(models.Model, ),
),
]

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# flake8: noqa
# Generated by Django 1.11.21 on 2019-06-18 16:17
from django.db import migrations, models
@@ -15,6 +15,10 @@ class Migration(migrations.Migration):
model_name='languageconfig',
name='item',
field=models.CharField(
choices=[('1', 'Exercises'), ('2', 'Ingredients')], editable=False, max_length=2),
choices=[
('1', 'Exercises'),
('2', 'Ingredients'),
], editable=False, max_length=2
),
),
]

View File

@@ -26,19 +26,18 @@ from django.utils.translation import gettext_lazy as _
# wger
from wger.core.models import (
Language,
UserProfile
UserProfile,
)
from wger.gym.helpers import is_any_gym_admin
from wger.gym.models import (
Gym,
GymUserConfig
GymUserConfig,
)
from wger.utils.cache import (
cache_mapper,
delete_template_fragment_cache
delete_template_fragment_cache,
)
logger = logging.getLogger(__name__)
@@ -55,24 +54,29 @@ class LanguageConfig(models.Model):
(SHOW_ITEM_INGREDIENTS, _('Ingredients')),
)
language = models.ForeignKey(Language,
related_name='language_source',
editable=False,
on_delete=models.CASCADE)
language_target = models.ForeignKey(Language,
related_name='language_target',
editable=False,
on_delete=models.CASCADE)
item = models.CharField(max_length=2,
choices=SHOW_ITEM_LIST,
editable=False)
language = models.ForeignKey(
Language,
related_name='language_source',
editable=False,
on_delete=models.CASCADE,
)
language_target = models.ForeignKey(
Language,
related_name='language_target',
editable=False,
on_delete=models.CASCADE,
)
item = models.CharField(max_length=2, choices=SHOW_ITEM_LIST, editable=False)
show = models.BooleanField(default=1)
class Meta:
"""
Set some other properties
"""
ordering = ["item", "language_target", ]
ordering = [
"item",
"language_target",
]
def __str__(self):
"""
@@ -117,15 +121,19 @@ class GymConfig(models.Model):
TODO: close registration (users can only become members thorough an admin)
"""
default_gym = models.ForeignKey(Gym,
verbose_name=_('Default gym'),
help_text=_('Select the default gym for this installation. '
'This will assign all new registered users to this '
'gym and update all existing users without a '
'gym.'),
null=True,
blank=True,
on_delete=models.CASCADE)
default_gym = models.ForeignKey(
Gym,
verbose_name=_('Default gym'),
help_text=_(
'Select the default gym for this installation. '
'This will assign all new registered users to this '
'gym and update all existing users without a '
'gym.'
),
null=True,
blank=True,
on_delete=models.CASCADE
)
"""
Default gym for the wger installation
"""

View File

@@ -14,7 +14,6 @@
#
# You should have received a copy of the GNU Affero General Public License
# Django
from django.db.models.signals import post_save
from django.dispatch import receiver

View File

@@ -41,23 +41,27 @@ class GymNameHeaderTestCase(WgerTestCase):
# Gym 1, custom header activated
gym = Gym.objects.get(pk=1)
self.assertTrue(gym.config.show_name)
for username in ('test',
'member1',
'member2',
'member3',
'member4',
'member5'):
for username in (
'test',
'member1',
'member2',
'member3',
'member4',
'member5',
):
self.user_login(username)
self.check_header(gym=gym.name)
# Gym 2, custom header deactivated
gym = Gym.objects.get(pk=2)
self.assertFalse(gym.config.show_name)
for username in ('trainer4',
'trainer5',
'demo',
'member6',
'member7',):
for username in (
'trainer4',
'trainer5',
'demo',
'member6',
'member7',
):
self.user_login(username)
self.check_header(gym=None)

View File

@@ -25,7 +25,7 @@ from wger.core.models import UserProfile
from wger.core.tests.base_testcase import WgerTestCase
from wger.gym.models import (
Gym,
GymUserConfig
GymUserConfig,
)
@@ -45,11 +45,13 @@ class GymConfigTestCase(WgerTestCase):
gym_config.save()
# Register
registration_data = {'username': 'myusername',
'password1': 'Aerieth4yuv5',
'password2': 'Aerieth4yuv5',
'email': 'my.email@example.com',
'g-recaptcha-response': 'PASSED', }
registration_data = {
'username': 'myusername',
'password1': 'Aerieth4yuv5',
'password2': 'Aerieth4yuv5',
'email': 'my.email@example.com',
'g-recaptcha-response': 'PASSED',
}
response = self.client.post(reverse('core:user:registration'), registration_data)
self.assertEqual(response.status_code, 302)
new_user = User.objects.all().last()
@@ -67,11 +69,13 @@ class GymConfigTestCase(WgerTestCase):
gym_config.save()
# Register
registration_data = {'username': 'myusername',
'password1': 'Iem2ahl1eizo',
'password2': 'Iem2ahl1eizo',
'email': 'my.email@example.com',
'g-recaptcha-response': 'PASSED', }
registration_data = {
'username': 'myusername',
'password1': 'Iem2ahl1eizo',
'password2': 'Iem2ahl1eizo',
'email': 'my.email@example.com',
'g-recaptcha-response': 'PASSED',
}
response = self.client.post(reverse('core:user:registration'), registration_data)
self.assertEqual(response.status_code, 302)

View File

@@ -22,30 +22,29 @@ from django.urls import path
# wger
from wger.config.views import (
gym_config,
language_config
language_config,
)
# sub patterns for language configs
patterns_language_config = [
path('<int:pk>/edit',
language_config.LanguageConfigUpdateView.as_view(),
name='edit'),
path('<int:pk>/edit', language_config.LanguageConfigUpdateView.as_view(), name='edit'),
]
# sub patterns for default gym
patterns_gym_config = [
path('edit',
gym_config.GymConfigUpdateView.as_view(),
name='edit'),
path('edit', gym_config.GymConfigUpdateView.as_view(), name='edit'),
]
#
# Actual patterns
#
urlpatterns = [
path('language-config/', include((patterns_language_config, 'language_config'), namespace="language_config")),
path('gym-config/', include((patterns_gym_config, 'gym_config'), namespace="gym_config")),
path(
'language-config/',
include((patterns_language_config, 'language_config'), namespace="language_config")
),
path(
'gym-config/',
include((patterns_gym_config, 'gym_config'), namespace="gym_config"),
),
]

View File

@@ -26,7 +26,6 @@ from django.views.generic import UpdateView
from wger.config.models import GymConfig
from wger.utils.generic_views import WgerFormMixin
logger = logging.getLogger(__name__)

View File

@@ -20,7 +20,7 @@ import logging
# Django
from django.contrib.auth.mixins import (
LoginRequiredMixin,
PermissionRequiredMixin
PermissionRequiredMixin,
)
from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy
@@ -30,14 +30,15 @@ from django.views.generic import UpdateView
from wger.config.models import LanguageConfig
from wger.utils.generic_views import WgerFormMixin
logger = logging.getLogger(__name__)
class LanguageConfigUpdateView(WgerFormMixin,
LoginRequiredMixin,
PermissionRequiredMixin,
UpdateView):
class LanguageConfigUpdateView(
WgerFormMixin,
LoginRequiredMixin,
PermissionRequiredMixin,
UpdateView,
):
"""
Generic view to edit a language config
"""

View File

@@ -15,10 +15,8 @@
# You should have received a copy of the GNU Affero General Public License
# along with Workout Manager. If not, see <http://www.gnu.org/licenses/>.
# wger
from wger import get_version
VERSION = get_version()
default_app_config = 'wger.core.apps.CoreConfig'

View File

@@ -30,7 +30,7 @@ from wger.core.models import (
License,
RepetitionUnit,
UserProfile,
WeightUnit
WeightUnit,
)
@@ -38,35 +38,38 @@ class UserprofileSerializer(serializers.ModelSerializer):
"""
Workout session serializer
"""
class Meta:
model = UserProfile
fields = ['user',
'gym',
'is_temporary',
'show_comments',
'show_english_ingredients',
'workout_reminder_active',
'workout_reminder',
'workout_duration',
'last_workout_notification',
'notification_language',
'timer_active',
'timer_active',
'age',
'birthdate',
'height',
'gender',
'sleep_hours',
'work_hours',
'work_intensity',
'sport_hours',
'sport_intensity',
'freetime_hours',
'freetime_intensity',
'calories',
'weight_unit',
'ro_access',
'num_days_weight_reminder']
fields = [
'user',
'gym',
'is_temporary',
'show_comments',
'show_english_ingredients',
'workout_reminder_active',
'workout_reminder',
'workout_duration',
'last_workout_notification',
'notification_language',
'timer_active',
'timer_active',
'age',
'birthdate',
'height',
'gender',
'sleep_hours',
'work_hours',
'work_intensity',
'sport_hours',
'sport_intensity',
'freetime_hours',
'freetime_intensity',
'calories',
'weight_unit',
'ro_access',
'num_days_weight_reminder',
]
class UsernameSerializer(serializers.Serializer):
@@ -88,11 +91,14 @@ class UserApiSerializer(serializers.ModelSerializer):
class UserRegistrationSerializer(serializers.ModelSerializer):
email = serializers.EmailField(
required=False,
validators=[UniqueValidator(queryset=User.objects.all())]
required=False, validators=[
UniqueValidator(queryset=User.objects.all()),
]
)
username = serializers.CharField(
required=True,
validators=[UniqueValidator(queryset=User.objects.all())],
)
username = serializers.CharField(required=True,
validators=[UniqueValidator(queryset=User.objects.all())])
password = serializers.CharField(write_only=True, required=True, validators=[validate_password])
class Meta:
@@ -113,16 +119,17 @@ class LanguageSerializer(serializers.ModelSerializer):
"""
Language serializer
"""
class Meta:
model = Language
fields = ['short_name',
'full_name']
fields = ['short_name', 'full_name']
class DaysOfWeekSerializer(serializers.ModelSerializer):
"""
DaysOfWeek serializer
"""
class Meta:
model = DaysOfWeek
fields = ['day_of_week']
@@ -132,28 +139,32 @@ class LicenseSerializer(serializers.ModelSerializer):
"""
License serializer
"""
class Meta:
model = License
fields = ['id',
'full_name',
'short_name',
'url']
fields = [
'id',
'full_name',
'short_name',
'url',
]
class RepetitionUnitSerializer(serializers.ModelSerializer):
"""
Repetition unit serializer
"""
class Meta:
model = RepetitionUnit
fields = ['id',
'name']
fields = ['id', 'name']
class WeightUnitSerializer(serializers.ModelSerializer):
"""
Weight unit serializer
"""
class Meta:
model = WeightUnit
fields = ['id', 'name']

View File

@@ -24,7 +24,7 @@ from django.contrib.auth.models import User
# Third Party
from rest_framework import (
status,
viewsets
viewsets,
)
from rest_framework.decorators import action
from rest_framework.permissions import AllowAny
@@ -42,7 +42,7 @@ from wger.core.api.serializers import (
UsernameSerializer,
UserprofileSerializer,
UserRegistrationSerializer,
WeightUnitSerializer
WeightUnitSerializer,
)
from wger.core.models import (
DaysOfWeek,
@@ -50,15 +50,14 @@ from wger.core.models import (
License,
RepetitionUnit,
UserProfile,
WeightUnit
WeightUnit,
)
from wger.utils.api_token import create_token
from wger.utils.permissions import (
UpdateOnlyPermission,
WgerPermission
WgerPermission,
)
logger = logging.getLogger(__name__)
@@ -97,7 +96,7 @@ class ApplicationVersionView(viewsets.ViewSet):
"""
Returns the application's version
"""
permission_classes = (AllowAny,)
permission_classes = (AllowAny, )
def get(self, request):
return Response(get_version())
@@ -107,7 +106,7 @@ class UserAPILoginView(viewsets.ViewSet):
"""
API endpoint for api user objects
"""
permission_classes = (AllowAny,)
permission_classes = (AllowAny, )
queryset = User.objects.all()
serializer_class = UserApiSerializer
throttle_scope = 'login'
@@ -127,17 +126,20 @@ class UserAPILoginView(viewsets.ViewSet):
user = User.objects.get(username=username)
except User.DoesNotExist:
logger.info(f"Tried logging via API with unknown user: '{username}'")
return Response({'detail': 'Username or password unknown'},
status=status.HTTP_401_UNAUTHORIZED)
return Response(
{'detail': 'Username or password unknown'},
status=status.HTTP_401_UNAUTHORIZED,
)
if user.check_password(password):
token = create_token(user)
return Response({'token': token.key},
status=status.HTTP_200_OK)
return Response({'token': token.key}, status=status.HTTP_200_OK)
else:
logger.info(f"User '{username}' tried logging via API with a wrong password")
return Response({'detail': 'Username or password unknown'},
status=status.HTTP_401_UNAUTHORIZED)
return Response(
{'detail': 'Username or password unknown'},
status=status.HTTP_401_UNAUTHORIZED,
)
class UserAPIRegistrationViewSet(viewsets.ViewSet):
@@ -162,9 +164,13 @@ class UserAPIRegistrationViewSet(viewsets.ViewSet):
user.userprofile.save()
token = create_token(user)
return Response({'message': 'api user successfully registered',
'token': token.key},
status=status.HTTP_201_CREATED)
return Response(
{
'message': 'api user successfully registered',
'token': token.key
},
status=status.HTTP_201_CREATED
)
class LanguageViewSet(viewsets.ReadOnlyModelViewSet):
@@ -174,8 +180,7 @@ class LanguageViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Language.objects.all()
serializer_class = LanguageSerializer
ordering_fields = '__all__'
filterset_fields = ('full_name',
'short_name')
filterset_fields = ('full_name', 'short_name')
class DaysOfWeekViewSet(viewsets.ReadOnlyModelViewSet):
@@ -195,9 +200,11 @@ class LicenseViewSet(viewsets.ReadOnlyModelViewSet):
queryset = License.objects.all()
serializer_class = LicenseSerializer
ordering_fields = '__all__'
filterset_fields = ('full_name',
'short_name',
'url')
filterset_fields = (
'full_name',
'short_name',
'url',
)
class RepetitionUnitViewSet(viewsets.ReadOnlyModelViewSet):

View File

@@ -35,19 +35,18 @@ from wger.manager.models import (
Set,
Setting,
Workout,
WorkoutLog
WorkoutLog,
)
from wger.nutrition.models import (
Ingredient,
IngredientWeightUnit,
Meal,
MealItem,
NutritionPlan
NutritionPlan,
)
from wger.utils.language import load_language
from wger.weight.models import WeightEntry
logger = logging.getLogger(__name__)
@@ -109,12 +108,14 @@ def create_demo_entries(user):
# Weight log entries
for reps in (8, 10, 12):
for i in range(1, 8):
log = WorkoutLog(user=user,
exercise=exercise,
workout=workout,
reps=reps,
weight=18 - reps + random.randint(1, 4),
date=datetime.date.today() - datetime.timedelta(weeks=i))
log = WorkoutLog(
user=user,
exercise=exercise,
workout=workout,
reps=reps,
weight=18 - reps + random.randint(1, 4),
date=datetime.date.today() - datetime.timedelta(weeks=i)
)
weight_log.append(log)
# French press
@@ -130,12 +131,14 @@ def create_demo_entries(user):
# Weight log entries
for reps in (7, 10):
for i in range(1, 8):
log = WorkoutLog(user=user,
exercise=exercise,
workout=workout,
reps=reps,
weight=30 - reps + random.randint(1, 4),
date=datetime.date.today() - datetime.timedelta(weeks=i))
log = WorkoutLog(
user=user,
exercise=exercise,
workout=workout,
reps=reps,
weight=30 - reps + random.randint(1, 4),
date=datetime.date.today() - datetime.timedelta(weeks=i)
)
weight_log.append(log)
# Squats
@@ -151,12 +154,14 @@ def create_demo_entries(user):
# Weight log entries
for reps in (5, 10, 12):
for i in range(1, 8):
log = WorkoutLog(user=user,
exercise=exercise,
workout=workout,
reps=reps,
weight=110 - reps + random.randint(1, 10),
date=datetime.date.today() - datetime.timedelta(weeks=i))
log = WorkoutLog(
user=user,
exercise=exercise,
workout=workout,
reps=reps,
weight=110 - reps + random.randint(1, 10),
date=datetime.date.today() - datetime.timedelta(weeks=i)
)
weight_log.append(log)
# Crunches
@@ -194,9 +199,11 @@ def create_demo_entries(user):
for i in range(1, 20):
creation_date = datetime.date.today() - datetime.timedelta(days=i)
if creation_date not in existing_entries:
entry = WeightEntry(user=user,
weight=80 + 0.5 * i + random.randint(1, 3),
date=creation_date)
entry = WeightEntry(
user=user,
weight=80 + 0.5 * i + random.randint(1, 3),
date=creation_date,
)
temp.append(entry)
WeightEntry.objects.bulk_create(temp)

View File

@@ -18,7 +18,7 @@
from django import forms
from django.contrib.auth.forms import (
AuthenticationForm,
UserCreationForm
UserCreationForm,
)
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
@@ -27,7 +27,7 @@ from django.forms import (
EmailField,
Form,
PasswordInput,
widgets
widgets,
)
from django.utils.translation import gettext as _
@@ -36,7 +36,7 @@ from captcha.fields import ReCaptchaField
from captcha.widgets import ReCaptchaV3
from crispy_forms.bootstrap import (
Accordion,
AccordionGroup
AccordionGroup,
)
from crispy_forms.helper import FormHelper
from crispy_forms.layout import (
@@ -44,7 +44,7 @@ from crispy_forms.layout import (
Column,
Layout,
Row,
Submit
Submit,
)
# wger
@@ -72,29 +72,30 @@ class UserLoginForm(AuthenticationForm):
class UserPreferencesForm(forms.ModelForm):
first_name = forms.CharField(label=_('First name'),
required=False)
last_name = forms.CharField(label=_('Last name'),
required=False)
email = EmailField(label=_("Email"),
help_text=_("Used for password resets and, optionally, e-mail reminders."),
required=False)
first_name = forms.CharField(label=_('First name'), required=False)
last_name = forms.CharField(label=_('Last name'), required=False)
email = EmailField(
label=_("Email"),
help_text=_("Used for password resets and, optionally, e-mail reminders."),
required=False
)
class Meta:
model = UserProfile
fields = ('show_comments',
'show_english_ingredients',
'workout_reminder_active',
'workout_reminder',
'workout_duration',
'notification_language',
'weight_unit',
'timer_active',
'timer_pause',
'ro_access',
'num_days_weight_reminder',
'birthdate'
)
fields = (
'show_comments',
'show_english_ingredients',
'workout_reminder_active',
'workout_reminder',
'workout_duration',
'notification_language',
'weight_unit',
'timer_active',
'timer_pause',
'ro_access',
'num_days_weight_reminder',
'birthdate',
)
def __init__(self, *args, **kwargs):
super(UserPreferencesForm, self).__init__(*args, **kwargs)
@@ -102,39 +103,44 @@ class UserPreferencesForm(forms.ModelForm):
self.helper.form_class = 'wger-form'
self.helper.layout = Layout(
Accordion(
AccordionGroup(_("Personal data"),
'email',
Row(Column('first_name', css_class='form-group col-6 mb-0'),
Column('last_name', css_class='form-group col-6 mb-0'),
css_class='form-row'),
),
AccordionGroup(_("Workout reminders"),
'workout_reminder_active',
'workout_reminder',
'workout_duration',
),
AccordionGroup(f"{_('Gym mode')} ({_('mobile version only')})",
"timer_active",
"timer_pause"
),
AccordionGroup(_("Other settings"),
"ro_access",
"notification_language",
"weight_unit",
"show_comments",
"show_english_ingredients",
"num_days_weight_reminder",
"birthdate",
)
),
ButtonHolder(Submit('submit', _("Save"), css_class='btn-success btn-block'))
AccordionGroup(
_("Personal data"),
'email',
Row(
Column('first_name', css_class='form-group col-6 mb-0'),
Column('last_name', css_class='form-group col-6 mb-0'),
css_class='form-row'
),
),
AccordionGroup(
_("Workout reminders"),
'workout_reminder_active',
'workout_reminder',
'workout_duration',
),
AccordionGroup(
f"{_('Gym mode')} ({_('mobile version only')})", "timer_active", "timer_pause"
),
AccordionGroup(
_("Other settings"),
"ro_access",
"notification_language",
"weight_unit",
"show_comments",
"show_english_ingredients",
"num_days_weight_reminder",
"birthdate",
)
), ButtonHolder(Submit('submit', _("Save"), css_class='btn-success btn-block'))
)
class UserEmailForm(forms.ModelForm):
email = EmailField(label=_("Email"),
help_text=_("Used for password resets and, optionally, email reminders."),
required=False)
email = EmailField(
label=_("Email"),
help_text=_("Used for password resets and, optionally, email reminders."),
required=False
)
class Meta:
model = User
@@ -164,10 +170,8 @@ class UserEmailForm(forms.ModelForm):
class UserPersonalInformationForm(UserEmailForm):
first_name = forms.CharField(label=_('First name'),
required=False)
last_name = forms.CharField(label=_('Last name'),
required=False)
first_name = forms.CharField(label=_('First name'), required=False)
last_name = forms.CharField(label=_('Last name'), required=False)
class Meta:
model = User
@@ -181,9 +185,11 @@ class PasswordConfirmationForm(Form):
This can be used to make sure the user really wants to perform a dangerous
action. The form must be initialised with a user object.
"""
password = CharField(label=_("Password"),
widget=PasswordInput,
help_text=_('Please enter your current password.'))
password = CharField(
label=_("Password"),
widget=PasswordInput,
help_text=_('Please enter your current password.')
)
def __init__(self, user, data=None):
self.user = user
@@ -209,23 +215,23 @@ class RegistrationForm(UserCreationForm, UserEmailForm):
Registration form with reCAPTCHA field
"""
captcha = ReCaptchaField(widget=ReCaptchaV3,
label='reCaptcha',
help_text=_('The form is secured with reCAPTCHA'))
captcha = ReCaptchaField(
widget=ReCaptchaV3,
label='reCaptcha',
help_text=_('The form is secured with reCAPTCHA'),
)
def __init__(self, *args, **kwargs):
super(RegistrationForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_class = 'wger-form'
self.helper.layout = Layout(
'username',
'email',
'username', 'email',
Row(
Column('password1', css_class='form-group col-6 mb-0'),
Column('password2', css_class='form-group col-6 mb-0'),
css_class='form-row'
),
'captcha',
), 'captcha',
ButtonHolder(Submit('submit', _("Register"), css_class='btn-success btn-block'))
)
@@ -240,14 +246,12 @@ class RegistrationFormNoCaptcha(UserCreationForm, UserEmailForm):
self.helper = FormHelper()
self.helper.form_class = 'wger-form'
self.helper.layout = Layout(
'username',
'email',
'username', 'email',
Row(
Column('password1', css_class='form-group col-6 mb-0'),
Column('password2', css_class='form-group col-6 mb-0'),
css_class='form-row'
),
ButtonHolder(Submit('submit', _("Register"), css_class='btn-success btn-block'))
), ButtonHolder(Submit('submit', _("Register"), css_class='btn-success btn-block'))
)
@@ -255,24 +259,30 @@ class FeedbackRegisteredForm(forms.Form):
"""
Feedback form used for logged in users
"""
contact = forms.CharField(max_length=50,
min_length=10,
label=_('Contact'),
help_text=_('Some way of answering you (e-mail, etc.)'),
required=False)
contact = forms.CharField(
max_length=50,
min_length=10,
label=_('Contact'),
help_text=_('Some way of answering you (e-mail, etc.)'),
required=False
)
comment = forms.CharField(max_length=500,
min_length=10,
widget=widgets.Textarea,
label=_('Comment'),
help_text=_('What do you want to say?'),
required=True)
comment = forms.CharField(
max_length=500,
min_length=10,
widget=widgets.Textarea,
label=_('Comment'),
help_text=_('What do you want to say?'),
required=True
)
class FeedbackAnonymousForm(FeedbackRegisteredForm):
"""
Feedback form used for anonymous users (has additionally a reCAPTCHA field)
"""
captcha = ReCaptchaField(widget=ReCaptchaV3,
label='reCaptcha',
help_text=_('The form is secured with reCAPTCHA'))
captcha = ReCaptchaField(
widget=ReCaptchaV3,
label='reCaptcha',
help_text=_('The form is secured with reCAPTCHA'),
)

View File

@@ -3,8 +3,6 @@ from django.core.management.base import BaseCommand
# wger
from wger.core.models import User
"""
Custom command permitting users to create user accounts
"""
@@ -22,7 +20,7 @@ class Command(BaseCommand):
'--disable',
action='store_true',
dest='disable',
default=False
default=False,
)
def handle(self, *args, **options):
@@ -33,13 +31,17 @@ class Command(BaseCommand):
if options['disable']:
user.userprofile.can_add_user = False
self.stdout.write(self.style.SUCCESS(
f"{options['name']} is now DISABLED from adding users via the API"))
self.stdout.write(
self.style.
SUCCESS(f"{options['name']} is now DISABLED from adding users via the API")
)
else:
user.userprofile.can_add_user = True
self.stdout.write(self.style.SUCCESS(
f"{options['name']} is now ALLOWED to add users via the API"))
self.stdout.write(
self.style.
SUCCESS(f"{options['name']} is now ALLOWED to add users via the API")
)
user.userprofile.save()
except Exception as exc:

View File

@@ -19,19 +19,19 @@ from django.contrib.auth.models import User
from django.core.cache import cache
from django.core.management.base import (
BaseCommand,
CommandError
CommandError,
)
# wger
from wger.core.models import Language
from wger.manager.models import (
Workout,
WorkoutLog
WorkoutLog,
)
from wger.utils.cache import (
delete_template_fragment_cache,
reset_workout_canonical_form,
reset_workout_log
reset_workout_log,
)
@@ -45,32 +45,39 @@ class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument('--clear-template',
action='store_true',
dest='clear_template',
default=False,
help='Clear only template caches')
parser.add_argument(
'--clear-template',
action='store_true',
dest='clear_template',
default=False,
help='Clear only template caches'
)
parser.add_argument('--clear-workout-cache',
action='store_true',
dest='clear_workout',
default=False,
help='Clear only the workout canonical view')
parser.add_argument(
'--clear-workout-cache',
action='store_true',
dest='clear_workout',
default=False,
help='Clear only the workout canonical view'
)
parser.add_argument('--clear-all',
action='store_true',
dest='clear_all',
default=False,
help='Clear ALL cached entries')
parser.add_argument(
'--clear-all',
action='store_true',
dest='clear_all',
default=False,
help='Clear ALL cached entries'
)
def handle(self, **options):
"""
Process the options
"""
if (not options['clear_template']
and not options['clear_workout']
and not options['clear_all']):
if (
not options['clear_template'] and not options['clear_workout']
and not options['clear_all']
):
raise CommandError('Please select what cache you need to delete, see help')
# Exercises, cached template fragments
@@ -86,16 +93,16 @@ class Command(BaseCommand):
if int(options['verbosity']) >= 3:
self.stdout.write(f" Year {entry.year}")
for month in WorkoutLog.objects.filter(user=user,
date__year=entry.year).dates('date',
'month'):
for month in WorkoutLog.objects.filter(user=user, date__year=entry.year
).dates('date', 'month'):
if int(options['verbosity']) >= 3:
self.stdout.write(f" Month {entry.month}")
reset_workout_log(user.id, entry.year, entry.month)
for day in WorkoutLog.objects.filter(user=user,
date__year=entry.year,
date__month=month.month).dates('date',
'day'):
for day in WorkoutLog.objects.filter(
user=user,
date__year=entry.year,
date__month=month.month,
).dates('date', 'day'):
if int(options['verbosity']) >= 3:
self.stdout.write(f" Day {day.day}")
reset_workout_log(user.id, entry.year, entry.month, day)

View File

@@ -22,7 +22,7 @@ from wger.core.models import RepetitionUnit
from wger.exercises.models import (
Equipment,
ExerciseCategory,
Muscle
Muscle,
)

View File

@@ -3,8 +3,6 @@ from django.core.management.base import BaseCommand
# wger
from wger.core.models import UserProfile
"""
List users registered via the API
"""

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# flake8: noqa
from django.db import models, migrations
from django.conf import settings
import django.core.validators
@@ -16,70 +16,318 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='DaysOfWeek',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
(
'id',
models.AutoField(
verbose_name='ID', serialize=False, auto_created=True, primary_key=True
)
),
('day_of_week', models.CharField(max_length=9, verbose_name='Day of the week')),
],
options={
'ordering': ['pk'],
},
bases=(models.Model,),
bases=(models.Model, ),
),
migrations.CreateModel(
name='Language',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
(
'id',
models.AutoField(
verbose_name='ID', serialize=False, auto_created=True, primary_key=True
)
),
('short_name', models.CharField(max_length=2, verbose_name='Language short name')),
('full_name', models.CharField(max_length=30, verbose_name='Language full name')),
],
options={
'ordering': ['full_name'],
},
bases=(models.Model,),
bases=(models.Model, ),
),
migrations.CreateModel(
name='License',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
(
'id',
models.AutoField(
verbose_name='ID', serialize=False, auto_created=True, primary_key=True
)
),
('full_name', models.CharField(max_length=60, verbose_name='Full name')),
('short_name', models.CharField(max_length=15, verbose_name='Short name, e.g. CC-BY-SA 3')),
('url', models.URLField(help_text='Link to license text or other information', null=True, verbose_name='Link', blank=True)),
(
'short_name',
models.CharField(max_length=15, verbose_name='Short name, e.g. CC-BY-SA 3')
),
(
'url',
models.URLField(
help_text='Link to license text or other information',
null=True,
verbose_name='Link',
blank=True
)
),
],
options={
'ordering': ['full_name'],
},
bases=(models.Model,),
bases=(models.Model, ),
),
migrations.CreateModel(
name='UserProfile',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
(
'id',
models.AutoField(
verbose_name='ID', serialize=False, auto_created=True, primary_key=True
)
),
('is_temporary', models.BooleanField(default=False, editable=False)),
('show_comments', models.BooleanField(default=True, help_text='Check to show exercise comments on the workout view', verbose_name='Show exercise comments')),
('show_english_ingredients', models.BooleanField(default=True, help_text='Check to also show ingredients in English while creating\na nutritional plan. These ingredients are extracted from a list provided\nby the US Department of Agriculture. It is extremely complete, with around\n7000 entries, but can be somewhat overwhelming and make the search difficult.', verbose_name='Also use ingredients in English')),
('workout_reminder_active', models.BooleanField(default=False, help_text='Check to activate automatic reminders for workouts. You need to provide a valid email for this to work.', verbose_name='Activate workout reminders')),
('workout_reminder', models.IntegerField(default=14, help_text='The number of days you want to be reminded before a workout expires.', verbose_name='Remind before expiration', validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(30)])),
('workout_duration', models.IntegerField(default=12, help_text='Default duration in weeks of workouts not in a schedule. Used for email workout reminders.', verbose_name='Default duration of workouts', validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(30)])),
(
'show_comments',
models.BooleanField(
default=True,
help_text='Check to show exercise comments on the workout view',
verbose_name='Show exercise comments'
)
),
(
'show_english_ingredients',
models.BooleanField(
default=True,
help_text=
'Check to also show ingredients in English while creating\na nutritional plan. These ingredients are extracted from a list provided\nby the US Department of Agriculture. It is extremely complete, with around\n7000 entries, but can be somewhat overwhelming and make the search difficult.',
verbose_name='Also use ingredients in English'
)
),
(
'workout_reminder_active',
models.BooleanField(
default=False,
help_text=
'Check to activate automatic reminders for workouts. You need to provide a valid email for this to work.',
verbose_name='Activate workout reminders'
)
),
(
'workout_reminder',
models.IntegerField(
default=14,
help_text=
'The number of days you want to be reminded before a workout expires.',
verbose_name='Remind before expiration',
validators=[
django.core.validators.MinValueValidator(1),
django.core.validators.MaxValueValidator(30)
]
)
),
(
'workout_duration',
models.IntegerField(
default=12,
help_text=
'Default duration in weeks of workouts not in a schedule. Used for email workout reminders.',
verbose_name='Default duration of workouts',
validators=[
django.core.validators.MinValueValidator(1),
django.core.validators.MaxValueValidator(30)
]
)
),
('last_workout_notification', models.DateField(null=True, editable=False)),
('timer_active', models.BooleanField(default=True, help_text='Check to activate timer pauses between exercises.', verbose_name='Use pauses in workout timer')),
('timer_pause', models.IntegerField(default=90, help_text='Default duration in seconds of pauses used by the timer in the gym mode.', verbose_name='Default duration of workout pauses', validators=[django.core.validators.MinValueValidator(10), django.core.validators.MaxValueValidator(400)])),
('age', models.IntegerField(max_length=2, null=True, verbose_name='Age', validators=[django.core.validators.MinValueValidator(10), django.core.validators.MaxValueValidator(100)])),
('height', models.IntegerField(max_length=2, null=True, verbose_name='Height (cm)', validators=[django.core.validators.MinValueValidator(140), django.core.validators.MaxValueValidator(230)])),
('gender', models.CharField(default=b'1', max_length=1, null=True, choices=[(b'1', 'Male'), (b'2', 'Female')])),
('sleep_hours', models.IntegerField(default=7, help_text='The average hours of sleep per day', null=True, verbose_name='Hours of sleep', validators=[django.core.validators.MinValueValidator(4), django.core.validators.MaxValueValidator(10)])),
('work_hours', models.IntegerField(default=8, help_text='Average hours per day', null=True, verbose_name='Work', validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(15)])),
('work_intensity', models.CharField(default=b'1', choices=[(b'1', 'Low'), (b'2', 'Medium'), (b'3', 'High')], max_length=1, help_text='Approximately', null=True, verbose_name='Physical intensity')),
('sport_hours', models.IntegerField(default=3, help_text='Average hours per week', null=True, verbose_name='Sport', validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(30)])),
('sport_intensity', models.CharField(default=b'2', choices=[(b'1', 'Low'), (b'2', 'Medium'), (b'3', 'High')], max_length=1, help_text='Approximately', null=True, verbose_name='Physical intensity')),
('freetime_hours', models.IntegerField(default=8, help_text='Average hours per day', null=True, verbose_name='Free time', validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(15)])),
('freetime_intensity', models.CharField(default=b'1', choices=[(b'1', 'Low'), (b'2', 'Medium'), (b'3', 'High')], max_length=1, help_text='Approximately', null=True, verbose_name='Physical intensity')),
('calories', models.IntegerField(default=2500, help_text='Total caloric intake, including e.g. any surplus', null=True, verbose_name='Total daily calories', validators=[django.core.validators.MinValueValidator(1500), django.core.validators.MaxValueValidator(5000)])),
('weight_unit', models.CharField(default=b'kg', max_length=2, verbose_name='Weight unit', choices=[(b'kg', 'Metric (kilogram)'), (b'lb', 'Imperial (pound)')])),
('gym', models.ForeignKey(blank=True, editable=False, to='gym.Gym', null=True, on_delete=models.CASCADE)),
('notification_language', models.ForeignKey(default=2, verbose_name='Notification language', to='core.Language', help_text='Language to use when sending you email notifications, e.g. email reminders for workouts. This does not affect the language used on the website.', on_delete=models.CASCADE)),
('user', models.OneToOneField(editable=False, to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
(
'timer_active',
models.BooleanField(
default=True,
help_text='Check to activate timer pauses between exercises.',
verbose_name='Use pauses in workout timer'
)
),
(
'timer_pause',
models.IntegerField(
default=90,
help_text=
'Default duration in seconds of pauses used by the timer in the gym mode.',
verbose_name='Default duration of workout pauses',
validators=[
django.core.validators.MinValueValidator(10),
django.core.validators.MaxValueValidator(400)
]
)
),
(
'age',
models.IntegerField(
max_length=2,
null=True,
verbose_name='Age',
validators=[
django.core.validators.MinValueValidator(10),
django.core.validators.MaxValueValidator(100)
]
)
),
(
'height',
models.IntegerField(
max_length=2,
null=True,
verbose_name='Height (cm)',
validators=[
django.core.validators.MinValueValidator(140),
django.core.validators.MaxValueValidator(230)
]
)
),
(
'gender',
models.CharField(
default=b'1',
max_length=1,
null=True,
choices=[(b'1', 'Male'), (b'2', 'Female')]
)
),
(
'sleep_hours',
models.IntegerField(
default=7,
help_text='The average hours of sleep per day',
null=True,
verbose_name='Hours of sleep',
validators=[
django.core.validators.MinValueValidator(4),
django.core.validators.MaxValueValidator(10)
]
)
),
(
'work_hours',
models.IntegerField(
default=8,
help_text='Average hours per day',
null=True,
verbose_name='Work',
validators=[
django.core.validators.MinValueValidator(1),
django.core.validators.MaxValueValidator(15)
]
)
),
(
'work_intensity',
models.CharField(
default=b'1',
choices=[(b'1', 'Low'), (b'2', 'Medium'), (b'3', 'High')],
max_length=1,
help_text='Approximately',
null=True,
verbose_name='Physical intensity'
)
),
(
'sport_hours',
models.IntegerField(
default=3,
help_text='Average hours per week',
null=True,
verbose_name='Sport',
validators=[
django.core.validators.MinValueValidator(1),
django.core.validators.MaxValueValidator(30)
]
)
),
(
'sport_intensity',
models.CharField(
default=b'2',
choices=[(b'1', 'Low'), (b'2', 'Medium'), (b'3', 'High')],
max_length=1,
help_text='Approximately',
null=True,
verbose_name='Physical intensity'
)
),
(
'freetime_hours',
models.IntegerField(
default=8,
help_text='Average hours per day',
null=True,
verbose_name='Free time',
validators=[
django.core.validators.MinValueValidator(1),
django.core.validators.MaxValueValidator(15)
]
)
),
(
'freetime_intensity',
models.CharField(
default=b'1',
choices=[(b'1', 'Low'), (b'2', 'Medium'), (b'3', 'High')],
max_length=1,
help_text='Approximately',
null=True,
verbose_name='Physical intensity'
)
),
(
'calories',
models.IntegerField(
default=2500,
help_text='Total caloric intake, including e.g. any surplus',
null=True,
verbose_name='Total daily calories',
validators=[
django.core.validators.MinValueValidator(1500),
django.core.validators.MaxValueValidator(5000)
]
)
),
(
'weight_unit',
models.CharField(
default=b'kg',
max_length=2,
verbose_name='Weight unit',
choices=[(b'kg', 'Metric (kilogram)'), (b'lb', 'Imperial (pound)')]
)
),
(
'gym',
models.ForeignKey(
blank=True,
editable=False,
to='gym.Gym',
null=True,
on_delete=models.CASCADE
)
),
(
'notification_language',
models.ForeignKey(
default=2,
verbose_name='Notification language',
to='core.Language',
help_text=
'Language to use when sending you email notifications, e.g. email reminders for workouts. This does not affect the language used on the website.',
on_delete=models.CASCADE
)
),
(
'user',
models.OneToOneField(
editable=False, to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE
)
),
],
options={
},
bases=(models.Model,),
options={},
bases=(models.Model, ),
),
]

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# flake8: noqa
from django.db import models, migrations
@@ -13,37 +13,70 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='userprofile',
name='ro_access',
field=models.BooleanField(help_text='Allow anonymous users to access your workouts and logs in a read-only mode. You can then share the links to social media.', verbose_name='Allow read-only access', default=False),
field=models.BooleanField(
help_text=
'Allow anonymous users to access your workouts and logs in a read-only mode. You can then share the links to social media.',
verbose_name='Allow read-only access',
default=False
),
preserve_default=True,
),
migrations.AlterField(
model_name='userprofile',
name='freetime_intensity',
field=models.CharField(choices=[('1', 'Low'), ('2', 'Medium'), ('3', 'High')], null=True, max_length=1, help_text='Approximately', default='1', verbose_name='Physical intensity'),
field=models.CharField(
choices=[('1', 'Low'), ('2', 'Medium'), ('3', 'High')],
null=True,
max_length=1,
help_text='Approximately',
default='1',
verbose_name='Physical intensity'
),
preserve_default=True,
),
migrations.AlterField(
model_name='userprofile',
name='gender',
field=models.CharField(choices=[('1', 'Male'), ('2', 'Female')], null=True, default='1', max_length=1),
field=models.CharField(
choices=[('1', 'Male'), ('2', 'Female')], null=True, default='1', max_length=1
),
preserve_default=True,
),
migrations.AlterField(
model_name='userprofile',
name='sport_intensity',
field=models.CharField(choices=[('1', 'Low'), ('2', 'Medium'), ('3', 'High')], null=True, max_length=1, help_text='Approximately', default='2', verbose_name='Physical intensity'),
field=models.CharField(
choices=[('1', 'Low'), ('2', 'Medium'), ('3', 'High')],
null=True,
max_length=1,
help_text='Approximately',
default='2',
verbose_name='Physical intensity'
),
preserve_default=True,
),
migrations.AlterField(
model_name='userprofile',
name='weight_unit',
field=models.CharField(choices=[('kg', 'Metric (kilogram)'), ('lb', 'Imperial (pound)')], verbose_name='Weight unit', max_length=2, default='kg'),
field=models.CharField(
choices=[('kg', 'Metric (kilogram)'), ('lb', 'Imperial (pound)')],
verbose_name='Weight unit',
max_length=2,
default='kg'
),
preserve_default=True,
),
migrations.AlterField(
model_name='userprofile',
name='work_intensity',
field=models.CharField(choices=[('1', 'Low'), ('2', 'Medium'), ('3', 'High')], null=True, max_length=1, help_text='Approximately', default='1', verbose_name='Physical intensity'),
field=models.CharField(
choices=[('1', 'Low'), ('2', 'Medium'), ('3', 'High')],
null=True,
max_length=1,
help_text='Approximately',
default='1',
verbose_name='Physical intensity'
),
preserve_default=True,
),
]

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# flake8: noqa
from django.db import models, migrations
import django.core.validators
@@ -14,25 +14,49 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='userprofile',
name='num_days_weight_reminder',
field=models.IntegerField(verbose_name='Automatic reminders for weight entries', max_length=30, null=True, help_text='Number of days after the last weight entry (enter 0 to deactivate)'),
field=models.IntegerField(
verbose_name='Automatic reminders for weight entries',
max_length=30,
null=True,
help_text='Number of days after the last weight entry (enter 0 to deactivate)'
),
preserve_default=True,
),
migrations.AlterField(
model_name='userprofile',
name='age',
field=models.IntegerField(verbose_name='Age', null=True, validators=[django.core.validators.MinValueValidator(10), django.core.validators.MaxValueValidator(100)]),
field=models.IntegerField(
verbose_name='Age',
null=True,
validators=[
django.core.validators.MinValueValidator(10),
django.core.validators.MaxValueValidator(100)
]
),
preserve_default=True,
),
migrations.AlterField(
model_name='userprofile',
name='height',
field=models.IntegerField(verbose_name='Height (cm)', null=True, validators=[django.core.validators.MinValueValidator(140), django.core.validators.MaxValueValidator(230)]),
field=models.IntegerField(
verbose_name='Height (cm)',
null=True,
validators=[
django.core.validators.MinValueValidator(140),
django.core.validators.MaxValueValidator(230)
]
),
preserve_default=True,
),
migrations.AlterField(
model_name='userprofile',
name='ro_access',
field=models.BooleanField(verbose_name='Allow external access', default=False, help_text='Allow external users to access your workouts and logs in a read-only mode. You need to set this before you can share links e.g. to social media.'),
field=models.BooleanField(
verbose_name='Allow external access',
default=False,
help_text=
'Allow external users to access your workouts and logs in a read-only mode. You need to set this before you can share links e.g. to social media.'
),
preserve_default=True,
),
]

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# flake8: noqa
from django.db import models, migrations
@@ -13,7 +13,12 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='userprofile',
name='num_days_weight_reminder',
field=models.IntegerField(default=0, verbose_name='Automatic reminders for weight entries', max_length=30, help_text='Number of days after the last weight entry (enter 0 to deactivate)'),
field=models.IntegerField(
default=0,
verbose_name='Automatic reminders for weight entries',
max_length=30,
help_text='Number of days after the last weight entry (enter 0 to deactivate)'
),
preserve_default=True,
),
]

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# flake8: noqa
from django.db import migrations, models
import django.core.validators
from django.conf import settings
@@ -16,14 +16,32 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='UserCache',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
(
'id',
models.AutoField(
auto_created=True, primary_key=True, serialize=False, verbose_name='ID'
)
),
('last_activity', models.DateField(null=True)),
('user', models.OneToOneField(editable=False, to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
(
'user',
models.OneToOneField(
editable=False, to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE
)
),
],
),
migrations.AlterField(
model_name='userprofile',
name='num_days_weight_reminder',
field=models.IntegerField(default=0, verbose_name='Automatic reminders for weight entries', help_text='Number of days after the last weight entry (enter 0 to deactivate)', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(30)]),
field=models.IntegerField(
default=0,
verbose_name='Automatic reminders for weight entries',
help_text='Number of days after the last weight entry (enter 0 to deactivate)',
validators=[
django.core.validators.MinValueValidator(0),
django.core.validators.MaxValueValidator(30)
]
),
),
]

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# flake8: noqa
from django.db import migrations, models
@@ -59,6 +59,4 @@ class Migration(migrations.Migration):
('manager', '0004_auto_20150609_1603'),
]
operations = [
migrations.RunPython(create_usercache, delete_usercache)
]
operations = [migrations.RunPython(create_usercache, delete_usercache)]

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# flake8: noqa
from django.db import migrations, models
@@ -13,7 +13,12 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='RepetitionUnit',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, primary_key=True, auto_created=True)),
(
'id',
models.AutoField(
verbose_name='ID', serialize=False, primary_key=True, auto_created=True
)
),
('name', models.CharField(verbose_name='Name', max_length=100)),
],
options={

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# flake8: noqa
from django.db import migrations, models
@@ -13,7 +13,12 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='WeightUnit',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
(
'id',
models.AutoField(
verbose_name='ID', serialize=False, auto_created=True, primary_key=True
)
),
('name', models.CharField(verbose_name='Name', max_length=100)),
],
options={

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# flake8: noqa
from django.db import migrations, models

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# flake8: noqa
# Generated by Django 1.9.12 on 2017-04-02 16:44
from django.db import migrations, models
import wger.core.models
@@ -15,11 +15,20 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='userprofile',
name='birthdate',
field=models.DateField(null=True, validators=[wger.core.models.birthdate_validator], verbose_name='Date of Birth'),
field=models.DateField(
null=True,
validators=[wger.core.models.birthdate_validator],
verbose_name='Date of Birth'
),
),
migrations.AlterField(
model_name='license',
name='full_name',
field=models.CharField(help_text='If a license has been localized, e.g. the Creative Commons licenses for the different countries, add them as separate entries here.', max_length=60, verbose_name='Full name'),
field=models.CharField(
help_text=
'If a license has been localized, e.g. the Creative Commons licenses for the different countries, add them as separate entries here.',
max_length=60,
verbose_name='Full name'
),
),
]

View File

@@ -15,10 +15,12 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='userprofile',
name='gym',
field=models.ForeignKey(blank=True,
editable=False,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to='gym.gym'),
field=models.ForeignKey(
blank=True,
editable=False,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to='gym.gym'
),
),
]

View File

@@ -16,11 +16,13 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='userprofile',
name='added_by',
field=models.ForeignKey(editable=False,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='added_by',
to=settings.AUTH_USER_MODEL),
field=models.ForeignKey(
editable=False,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='added_by',
to=settings.AUTH_USER_MODEL
),
),
migrations.AddField(
model_name='userprofile',

View File

@@ -23,7 +23,7 @@ from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
from django.core.validators import (
MaxValueValidator,
MinValueValidator
MinValueValidator,
)
from django.db import models
from django.db.models import IntegerField
@@ -43,18 +43,18 @@ class Language(models.Model):
"""
# e.g. 'de'
short_name = models.CharField(max_length=2,
verbose_name=_('Language short name'))
short_name = models.CharField(max_length=2, verbose_name=_('Language short name'))
# e.g. 'Deutsch'
full_name = models.CharField(max_length=30,
verbose_name=_('Language full name'))
full_name = models.CharField(max_length=30, verbose_name=_('Language full name'))
class Meta:
"""
Set Meta options
"""
ordering = ["full_name", ]
ordering = [
"full_name",
]
#
# Django methods
@@ -117,27 +117,30 @@ class UserProfile(models.Model):
UNITS_LB = 'lb'
UNITS = (
(UNITS_KG, _('Metric (kilogram)')),
(UNITS_LB, _('Imperial (pound)'))
(UNITS_LB, _('Imperial (pound)')),
)
user = models.OneToOneField(User,
editable=False,
on_delete=models.CASCADE)
user = models.OneToOneField(
User,
editable=False,
on_delete=models.CASCADE,
)
"""
The user
"""
gym = models.ForeignKey(Gym,
editable=False,
null=True,
blank=True,
on_delete=models.SET_NULL)
gym = models.ForeignKey(
Gym,
editable=False,
null=True,
blank=True,
on_delete=models.SET_NULL,
)
"""
The gym this user belongs to, if any
"""
is_temporary = models.BooleanField(default=False,
editable=False)
is_temporary = models.BooleanField(default=False, editable=False)
"""
Flag to mark a temporary user (demo account)
"""
@@ -146,10 +149,12 @@ class UserProfile(models.Model):
# User preferences
#
show_comments = models.BooleanField(verbose_name=_('Show exercise comments'),
help_text=_('Check to show exercise comments on the '
'workout view'),
default=True)
show_comments = models.BooleanField(
verbose_name=_('Show exercise comments'),
help_text=_('Check to show exercise comments on the '
'workout view'),
default=True,
)
"""
Show exercise comments on workout view
"""
@@ -158,33 +163,44 @@ class UserProfile(models.Model):
# (obviously this is only meaningful if the user has a language other than english)
show_english_ingredients = models.BooleanField(
verbose_name=_('Also use ingredients in English'),
help_text=_("""Check to also show ingredients in English while creating
help_text=_(
"""Check to also show ingredients in English while creating
a nutritional plan. These ingredients are extracted from a list provided
by the US Department of Agriculture. It is extremely complete, with around
7000 entries, but can be somewhat overwhelming and make the search difficult."""),
default=True)
7000 entries, but can be somewhat overwhelming and make the search difficult."""
),
default=True
)
workout_reminder_active = models.BooleanField(verbose_name=_('Activate workout reminders'),
help_text=_('Check to activate automatic '
'reminders for workouts. You need '
'to provide a valid email for this '
'to work.'),
default=False)
workout_reminder_active = models.BooleanField(
verbose_name=_('Activate workout reminders'),
help_text=_(
'Check to activate automatic '
'reminders for workouts. You need '
'to provide a valid email for this '
'to work.'
),
default=False
)
workout_reminder = IntegerField(verbose_name=_('Remind before expiration'),
help_text=_('The number of days you want to be reminded '
'before a workout expires.'),
default=14,
validators=[MinValueValidator(1), MaxValueValidator(30)])
workout_duration = IntegerField(verbose_name=_('Default duration of workouts'),
help_text=_('Default duration in weeks of workouts not '
'in a schedule. Used for email workout '
'reminders.'),
default=12,
validators=[MinValueValidator(1), MaxValueValidator(30)])
last_workout_notification = models.DateField(editable=False,
blank=False,
null=True)
workout_reminder = IntegerField(
verbose_name=_('Remind before expiration'),
help_text=_('The number of days you want to be reminded '
'before a workout expires.'),
default=14,
validators=[MinValueValidator(1), MaxValueValidator(30)]
)
workout_duration = IntegerField(
verbose_name=_('Default duration of workouts'),
help_text=_(
'Default duration in weeks of workouts not '
'in a schedule. Used for email workout '
'reminders.'
),
default=12,
validators=[MinValueValidator(1), MaxValueValidator(30)]
)
last_workout_notification = models.DateField(editable=False, blank=False, null=True)
"""
The last time the user got a workout reminder email
@@ -192,28 +208,36 @@ by the US Department of Agriculture. It is extremely complete, with around
send users an email once per week
"""
notification_language = models.ForeignKey(Language,
verbose_name=_('Notification language'),
help_text=_('Language to use when sending you email '
'notifications, e.g. email reminders for '
'workouts. This does not affect the '
'language used on the website.'),
default=2,
on_delete=models.CASCADE)
notification_language = models.ForeignKey(
Language,
verbose_name=_('Notification language'),
help_text=_(
'Language to use when sending you email '
'notifications, e.g. email reminders for '
'workouts. This does not affect the '
'language used on the website.'
),
default=2,
on_delete=models.CASCADE
)
timer_active = models.BooleanField(verbose_name=_('Use pauses in workout timer'),
help_text=_('Check to activate timer pauses between '
'exercises.'),
default=True)
timer_active = models.BooleanField(
verbose_name=_('Use pauses in workout timer'),
help_text=_('Check to activate timer pauses between '
'exercises.'),
default=True
)
"""
Switch to activate pauses in the gym view
"""
timer_pause = IntegerField(verbose_name=_('Default duration of workout pauses'),
help_text=_('Default duration in seconds of pauses used by '
'the timer in the gym mode.'),
default=90,
validators=[MinValueValidator(10), MaxValueValidator(400)])
timer_pause = IntegerField(
verbose_name=_('Default duration of workout pauses'),
help_text=_('Default duration in seconds of pauses used by '
'the timer in the gym mode.'),
default=90,
validators=[MinValueValidator(10), MaxValueValidator(400)]
)
"""
Default duration of workout pauses in the gym view
"""
@@ -221,137 +245,168 @@ by the US Department of Agriculture. It is extremely complete, with around
#
# User statistics
#
age = IntegerField(verbose_name=_('Age'),
blank=False,
null=True,
validators=[MinValueValidator(10), MaxValueValidator(100)])
age = IntegerField(
verbose_name=_('Age'),
blank=False,
null=True,
validators=[MinValueValidator(10), MaxValueValidator(100)]
)
"""The user's age"""
birthdate = models.DateField(verbose_name=('Date of Birth'),
blank=False,
null=True,
validators=[birthdate_validator])
birthdate = models.DateField(
verbose_name=('Date of Birth'),
blank=False,
null=True,
validators=[birthdate_validator],
)
"""The user's date of birth"""
height = IntegerField(verbose_name=_('Height (cm)'),
blank=False,
validators=[MinValueValidator(140), MaxValueValidator(230)],
null=True)
height = IntegerField(
verbose_name=_('Height (cm)'),
blank=False,
validators=[MinValueValidator(140), MaxValueValidator(230)],
null=True
)
"""The user's height"""
gender = models.CharField(max_length=1,
choices=GENDER,
default=GENDER_MALE,
blank=False,
null=True)
gender = models.CharField(
max_length=1,
choices=GENDER,
default=GENDER_MALE,
blank=False,
null=True,
)
"""Gender"""
sleep_hours = IntegerField(verbose_name=_('Hours of sleep'),
help_text=_('The average hours of sleep per day'),
default=7,
blank=False,
null=True,
validators=[MinValueValidator(4), MaxValueValidator(10)])
sleep_hours = IntegerField(
verbose_name=_('Hours of sleep'),
help_text=_('The average hours of sleep per day'),
default=7,
blank=False,
null=True,
validators=[MinValueValidator(4), MaxValueValidator(10)]
)
"""The average hours of sleep per day"""
work_hours = IntegerField(verbose_name=_('Work'),
help_text=_('Average hours per day'),
default=8,
blank=False,
null=True,
validators=[MinValueValidator(1), MaxValueValidator(15)])
work_hours = IntegerField(
verbose_name=_('Work'),
help_text=_('Average hours per day'),
default=8,
blank=False,
null=True,
validators=[MinValueValidator(1), MaxValueValidator(15)]
)
"""The average hours at work per day"""
work_intensity = models.CharField(verbose_name=_('Physical intensity'),
help_text=_('Approximately'),
max_length=1,
choices=INTENSITY,
default=INTENSITY_LOW,
blank=False,
null=True)
work_intensity = models.CharField(
verbose_name=_('Physical intensity'),
help_text=_('Approximately'),
max_length=1,
choices=INTENSITY,
default=INTENSITY_LOW,
blank=False,
null=True
)
"""Physical intensity of work"""
sport_hours = IntegerField(verbose_name=_('Sport'),
help_text=_('Average hours per week'),
default=3,
blank=False,
null=True,
validators=[MinValueValidator(1), MaxValueValidator(30)])
sport_hours = IntegerField(
verbose_name=_('Sport'),
help_text=_('Average hours per week'),
default=3,
blank=False,
null=True,
validators=[MinValueValidator(1), MaxValueValidator(30)]
)
"""The average hours performing sports per week"""
sport_intensity = models.CharField(verbose_name=_('Physical intensity'),
help_text=_('Approximately'),
max_length=1,
choices=INTENSITY,
default=INTENSITY_MEDIUM,
blank=False,
null=True)
sport_intensity = models.CharField(
verbose_name=_('Physical intensity'),
help_text=_('Approximately'),
max_length=1,
choices=INTENSITY,
default=INTENSITY_MEDIUM,
blank=False,
null=True
)
"""Physical intensity of sport activities"""
freetime_hours = IntegerField(verbose_name=_('Free time'),
help_text=_('Average hours per day'),
default=8,
blank=False,
null=True,
validators=[MinValueValidator(1), MaxValueValidator(15)])
freetime_hours = IntegerField(
verbose_name=_('Free time'),
help_text=_('Average hours per day'),
default=8,
blank=False,
null=True,
validators=[MinValueValidator(1), MaxValueValidator(15)]
)
"""The average hours of free time per day"""
freetime_intensity = models.CharField(verbose_name=_('Physical intensity'),
help_text=_('Approximately'),
max_length=1,
choices=INTENSITY,
default=INTENSITY_LOW,
blank=False,
null=True)
freetime_intensity = models.CharField(
verbose_name=_('Physical intensity'),
help_text=_('Approximately'),
max_length=1,
choices=INTENSITY,
default=INTENSITY_LOW,
blank=False,
null=True
)
"""Physical intensity during free time"""
calories = IntegerField(verbose_name=_('Total daily calories'),
help_text=_('Total caloric intake, including e.g. any surplus'),
default=2500,
blank=False,
null=True,
validators=[MinValueValidator(1500), MaxValueValidator(5000)])
calories = IntegerField(
verbose_name=_('Total daily calories'),
help_text=_('Total caloric intake, including e.g. any surplus'),
default=2500,
blank=False,
null=True,
validators=[MinValueValidator(1500), MaxValueValidator(5000)]
)
"""Basic caloric intake based on physical activity"""
#
# Others
#
weight_unit = models.CharField(verbose_name=_('Weight unit'),
max_length=2,
choices=UNITS,
default=UNITS_KG)
weight_unit = models.CharField(
verbose_name=_('Weight unit'),
max_length=2,
choices=UNITS,
default=UNITS_KG,
)
"""Preferred weight unit"""
ro_access = models.BooleanField(verbose_name=_('Allow external access'),
help_text=_('Allow external users to access your workouts and '
'logs in a read-only mode. You need to set this '
'before you can share links e.g. to social media.'),
default=False)
ro_access = models.BooleanField(
verbose_name=_('Allow external access'),
help_text=_(
'Allow external users to access your workouts and '
'logs in a read-only mode. You need to set this '
'before you can share links e.g. to social media.'
),
default=False
)
"""Allow anonymous read-only access"""
num_days_weight_reminder = models.IntegerField(verbose_name=_('Automatic reminders for weight '
'entries'),
help_text=_('Number of days after the last '
'weight entry (enter 0 to '
'deactivate)'),
validators=[MinValueValidator(0),
MaxValueValidator(30)],
default=0)
num_days_weight_reminder = models.IntegerField(
verbose_name=_('Automatic reminders for weight '
'entries'),
help_text=_('Number of days after the last '
'weight entry (enter 0 to '
'deactivate)'),
validators=[MinValueValidator(0), MaxValueValidator(30)],
default=0
)
"""Number of Days for email weight reminder"""
#
# API
#
added_by = models.ForeignKey(User,
editable=False,
null=True,
related_name='added_by',
on_delete=models.CASCADE)
added_by = models.ForeignKey(
User,
editable=False,
null=True,
related_name='added_by',
on_delete=models.CASCADE,
)
"""User that originally registered this user via REST API"""
can_add_user = models.BooleanField(default=False,
editable=False)
can_add_user = models.BooleanField(default=False, editable=False)
"""
Flag to indicate whether the (app) user can register other users on his
behalf over the REST API
@@ -374,10 +429,12 @@ by the US Department of Agriculture. It is extremely complete, with around
"""
Return the address as saved in the current contract (user's gym)
"""
out = {'zip_code': '',
'city': '',
'street': '',
'phone': ''}
out = {
'zip_code': '',
'city': '',
'street': '',
'phone': '',
}
if self.user.contract_member.exists():
last_contract = self.user.contract_member.last()
out['zip_code'] = last_contract.zip_code
@@ -391,8 +448,10 @@ by the US Department of Agriculture. It is extremely complete, with around
"""
Make sure the total amount of hours is 24
"""
if ((self.sleep_hours and self.freetime_hours and self.work_hours)
and (self.sleep_hours + self.freetime_hours + self.work_hours) > 24):
if (
(self.sleep_hours and self.freetime_hours and self.work_hours)
and (self.sleep_hours + self.freetime_hours + self.work_hours) > 24
):
raise ValidationError(_('The sum of all hours has to be 24'))
def __str__(self):
@@ -436,10 +495,12 @@ by the US Department of Agriculture. It is extremely complete, with around
weight = self.weight if self.use_metric else AbstractWeight(self.weight, 'lb').kg
try:
rate = ((10 * weight) # in kg
+ (decimal.Decimal(6.25) * self.height) # in cm
- (5 * self.age) # in years
+ factor)
rate = (
(10 * weight) # in kg
+ (decimal.Decimal(6.25) * self.height) # in cm
- (5 * self.age) # in years
+ factor
)
# Any of the entries is missing
except TypeError:
rate = 0
@@ -492,10 +553,12 @@ by the US Department of Agriculture. It is extremely complete, with around
"""
Create a new weight entry as needed
"""
if (not WeightEntry.objects.filter(user=self.user).exists()
or (datetime.date.today()
- WeightEntry.objects.filter(user=self.user).latest().date
> datetime.timedelta(days=3))):
if (
not WeightEntry.objects.filter(user=self.user).exists() or (
datetime.date.today() - WeightEntry.objects.filter(user=self.user).latest().date >
datetime.timedelta(days=3)
)
):
entry = WeightEntry()
entry.weight = weight
entry.user = self.user
@@ -548,14 +611,15 @@ class DaysOfWeek(models.Model):
This model is needed so that 'Day' can have multiple days of the week selected
"""
day_of_week = models.CharField(max_length=9,
verbose_name=_('Day of the week'))
day_of_week = models.CharField(max_length=9, verbose_name=_('Day of the week'))
class Meta:
"""
Order by day-ID, this is needed for some DBs
"""
ordering = ["pk", ]
ordering = [
"pk",
]
def __str__(self):
"""
@@ -569,28 +633,35 @@ class License(models.Model):
License for an item (exercise, ingredient, etc.)
"""
full_name = models.CharField(max_length=60,
verbose_name=_('Full name'),
help_text=_('If a license has been localized, e.g. the Creative '
'Commons licenses for the different countries, add '
'them as separate entries here.'))
full_name = models.CharField(
max_length=60,
verbose_name=_('Full name'),
help_text=_(
'If a license has been localized, e.g. the Creative '
'Commons licenses for the different countries, add '
'them as separate entries here.'
)
)
"""Full name"""
short_name = models.CharField(max_length=15,
verbose_name=_('Short name, e.g. CC-BY-SA 3'))
short_name = models.CharField(max_length=15, verbose_name=_('Short name, e.g. CC-BY-SA 3'))
"""Short name, e.g. CC-BY-SA 3"""
url = models.URLField(verbose_name=_('Link'),
help_text=_('Link to license text or other information'),
blank=True,
null=True)
url = models.URLField(
verbose_name=_('Link'),
help_text=_('Link to license text or other information'),
blank=True,
null=True
)
"""URL to full license text or other information"""
class Meta:
"""
Set Meta options
"""
ordering = ["full_name", ]
ordering = [
"full_name",
]
#
# Django methods
@@ -620,10 +691,11 @@ class RepetitionUnit(models.Model):
"""
Set Meta options
"""
ordering = ["name", ]
ordering = [
"name",
]
name = models.CharField(max_length=100,
verbose_name=_('Name'))
name = models.CharField(max_length=100, verbose_name=_('Name'))
def __str__(self):
"""
@@ -659,10 +731,11 @@ class WeightUnit(models.Model):
"""
Set Meta options
"""
ordering = ["name", ]
ordering = [
"name",
]
name = models.CharField(max_length=100,
verbose_name=_('Name'))
name = models.CharField(max_length=100, verbose_name=_('Name'))
def __str__(self):
"""

View File

@@ -14,7 +14,6 @@
#
# You should have received a copy of the GNU Affero General Public License
# Standard Library
import datetime
@@ -22,14 +21,14 @@ import datetime
from django.contrib.auth.models import User
from django.db.models.signals import (
post_save,
pre_save
pre_save,
)
from django.dispatch import receiver
# wger
from wger.core.models import (
UserCache,
UserProfile
UserProfile,
)
from wger.utils.helpers import disable_for_loaddata
@@ -61,8 +60,10 @@ def set_user_age(sender, instance, **kwargs):
if instance.age is None and instance.birthdate is not None:
today = datetime.date.today()
birthday = instance.birthdate
instance.age = (today.year - birthday.year
- ((today.month, today.day) < (birthday.month, birthday.day)))
instance.age = (
today.year - birthday.year -
((today.month, today.day) < (birthday.month, birthday.day))
)
post_save.connect(create_user_profile, sender=User)

View File

@@ -23,16 +23,15 @@ from django.utils.html import strip_spaces_between_tags
from django.utils.safestring import mark_safe
from django.utils.translation import (
gettext_lazy as _,
pgettext
pgettext,
)
# wger
from wger.utils.constants import (
PAGINATION_MAX_TOTAL_PAGES,
PAGINATION_PAGES_AROUND_CURRENT
PAGINATION_PAGES_AROUND_CURRENT,
)
register = template.Library()
@@ -53,9 +52,11 @@ def render_day(day, editable=True):
"""
Renders a day as it will be displayed in the workout overview
"""
return {'day': day.canonical_representation,
'workout': day.training,
'editable': editable}
return {
'day': day.canonical_representation,
'workout': day.training,
'editable': editable,
}
@register.inclusion_tag('tags/pagination.html')
@@ -86,8 +87,7 @@ def pagination(paginator, page):
page_range = paginator.page_range
# Set the template variables
return {'page': page,
'page_range': page_range}
return {'page': page, 'page_range': page_range}
@register.inclusion_tag('tags/render_weight_log.html')
@@ -96,9 +96,11 @@ def render_weight_log(log, div_uuid, user=None):
Renders a weight log series
"""
return {'log': log,
'div_uuid': div_uuid,
'user': user}
return {
'log': log,
'div_uuid': div_uuid,
'user': user,
}
@register.inclusion_tag('tags/license-sidebar.html')
@@ -107,8 +109,7 @@ def license_sidebar(license, author=None):
Renders the license notice for exercises
"""
return {'license': license,
'author': author}
return {'license': license, 'author': author}
@register.inclusion_tag('tags/muscles.html')
@@ -136,8 +137,7 @@ def render_muscles(muscles=None, muscles_sec=None):
+ [i.image_url_secondary for i in out_sec] \
+ [static(f"images/muscles/muscular_system_{front_back}.svg")]
return {"backgrounds": backgrounds,
"empty": False}
return {"backgrounds": backgrounds, "empty": False}
@register.inclusion_tag('tags/language_select.html', takes_context=True)
@@ -146,9 +146,11 @@ def language_select(context, language):
Renders a link to change the current language.
"""
return {'language_name': language[1],
'path': f'images/icons/flag-{language[0]}.svg',
'i18n_path': context['i18n_path'][language[0]]}
return {
'language_name': language[1],
'path': f'images/icons/flag-{language[0]}.svg',
'i18n_path': context['i18n_path'][language[0]]
}
@register.filter
@@ -231,6 +233,7 @@ def format_username(user):
class SpacelessNode(template.base.Node):
def __init__(self, nodelist):
self.nodelist = nodelist
@@ -247,6 +250,6 @@ def spaceless_config(parser, token):
This is django's spaceless tag, copied here to use our configurable
SpacelessNode
"""
nodelist = parser.parse(('endspaceless_config',))
nodelist = parser.parse(('endspaceless_config', ))
parser.delete_first_token()
return SpacelessNode(nodelist)

View File

@@ -12,7 +12,6 @@
#
# You should have received a copy of the GNU Affero General Public License
# Django
from django.contrib.auth.models import User
@@ -103,6 +102,7 @@ class ApiGetTestCase(object):
"""
Base test case for testing GET access to the API
"""
def test_ordering(self):
"""
Test that ordering the resource works
@@ -303,8 +303,10 @@ class ApiPatchTestCase(object):
# Different logged in user
self.get_credentials(self.user_fail)
response = self.client.patch(self.url_detail, data=self.data)
self.assertIn(response.status_code,
(status.HTTP_403_FORBIDDEN, status.HTTP_404_NOT_FOUND))
self.assertIn(
response.status_code,
(status.HTTP_403_FORBIDDEN, status.HTTP_404_NOT_FOUND),
)
else:
# Anonymous user
response = self.client.patch(self.url_detail, data=self.data)
@@ -402,8 +404,10 @@ class ApiPutTestCase(object):
# print(f'201: {self.url_detail}')
obj = self.resource.objects.get(pk=response.data['id'])
obj2 = self.resource.objects.get(pk=self.pk)
self.assertNotEqual(obj.get_owner_object().user.username,
obj2.get_owner_object().user.username)
self.assertNotEqual(
obj.get_owner_object().user.username,
obj2.get_owner_object().user.username,
)
self.assertEqual(obj.get_owner_object().user.username, self.user_fail)
self.assertEqual(count_before + 1, count_after)
@@ -576,14 +580,15 @@ class ApiDeleteTestCase(object):
self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
class ApiBaseResourceTestCase(BaseTestCase,
ApiBaseTestCase,
ApiGetTestCase,
ApiPostTestCase,
ApiDeleteTestCase,
ApiPutTestCase,
ApiPatchTestCase):
class ApiBaseResourceTestCase(
BaseTestCase,
ApiBaseTestCase,
ApiGetTestCase,
ApiPostTestCase,
ApiDeleteTestCase,
ApiPutTestCase,
ApiPatchTestCase,
):
"""
Base test case for the REST API

View File

@@ -26,14 +26,13 @@ from django.core.cache import cache
from django.test import TestCase
from django.urls import (
NoReverseMatch,
reverse
reverse,
)
from django.utils.translation import activate
# wger
from wger.utils.constants import TWOPLACES
STATUS_CODES_FAIL = (302, 403, 404)
@@ -73,15 +72,19 @@ def delete_testcase_add_methods(cls):
"""
for user in get_user_list(cls.user_fail):
def test_unauthorized(self):
self.user_login(user)
self.delete_object(fail=False)
setattr(cls, f'test_unauthorized_{user}', test_unauthorized)
for user in get_user_list(cls.user_success):
def test_authorized(self):
self.user_login(user)
self.delete_object(fail=False)
setattr(cls, f'test_authorized_{user}', test_authorized)
@@ -93,34 +96,36 @@ class BaseTestCase(object):
REST API tests
"""
fixtures = ('days_of_week',
'gym_config',
'groups',
'setting_repetition_units',
'setting_weight_units',
'test-languages',
'test-licenses',
'test-gyms',
'test-gymsconfig',
'test-user-data',
'test-gym-adminconfig.json',
'test-gym-userconfig.json',
'test-admin-user-notes',
'test-gym-user-documents',
'test-contracts',
'test-apikeys',
'test-weight-data',
'test-equipment',
'test-exercises',
'test-exercise-images',
'test-weight-units',
'test-ingredients',
'test-nutrition-data',
'test-nutrition-diary',
'test-workout-data',
'test-workout-session',
'test-schedules',
'test-gallery-images')
fixtures = (
'days_of_week',
'gym_config',
'groups',
'setting_repetition_units',
'setting_weight_units',
'test-languages',
'test-licenses',
'test-gyms',
'test-gymsconfig',
'test-user-data',
'test-gym-adminconfig.json',
'test-gym-userconfig.json',
'test-admin-user-notes',
'test-gym-user-documents',
'test-contracts',
'test-apikeys',
'test-weight-data',
'test-equipment',
'test-exercises',
'test-exercise-images',
'test-weight-units',
'test-ingredients',
'test-nutrition-data',
'test-nutrition-diary',
'test-workout-data',
'test-workout-session',
'test-schedules',
'test-gallery-images',
)
current_user = 'anonymous'
current_password = ''
@@ -268,9 +273,9 @@ class WgerDeleteTestCase(WgerTestCase):
else:
self.assertEqual(response.status_code, 302)
self.assertEqual(count_before - 1, count_after)
self.assertRaises(self.object_class.DoesNotExist,
self.object_class.objects.get,
pk=self.pk)
self.assertRaises(
self.object_class.DoesNotExist, self.object_class.objects.get, pk=self.pk
)
# TODO: the redirection page might not have a language prefix (e.g. /user/login
# instead of /en/user/login) so there is an additional redirect

View File

@@ -25,7 +25,6 @@ from rest_framework.authtoken.models import Token
# wger
from wger.core.tests.base_testcase import WgerTestCase
logger = logging.getLogger(__name__)

View File

@@ -22,7 +22,6 @@ from django.urls import reverse
# wger
from wger.core.tests.base_testcase import WgerTestCase
logger = logging.getLogger(__name__)
@@ -42,9 +41,11 @@ class ChangePasswordTestCase(WgerTestCase):
self.assertEqual(response.status_code, 200)
# Fill in the change password form
form_data = {'old_password': 'testtest',
'new_password1': 'shuZoh2oGu7i',
'new_password2': 'shuZoh2oGu7i'}
form_data = {
'old_password': 'testtest',
'new_password1': 'shuZoh2oGu7i',
'new_password2': 'shuZoh2oGu7i',
}
response = self.client.post(reverse('core:user:change-password'), form_data)
self.assertEqual(response.status_code, 302)

View File

@@ -22,7 +22,6 @@ from django.urls import reverse
# wger
from wger.core.tests.base_testcase import WgerTestCase
logger = logging.getLogger(__name__)
@@ -44,8 +43,10 @@ class DeleteUserTestCase(WgerTestCase):
# Wrong user password
if not fail:
response = self.client.post(reverse('core:user:delete'),
{'password': 'not the user password'})
response = self.client.post(
reverse('core:user:delete'),
{'password': 'not the user password'},
)
self.assertEqual(response.status_code, 200)
self.assertEqual(User.objects.filter(username='test').count(), 1)
@@ -83,22 +84,27 @@ class DeleteUserByAdminTestCase(WgerTestCase):
response = self.client.get(reverse('core:user:delete', kwargs={'user_pk': 2}))
self.assertEqual(User.objects.filter(username='test').count(), 1)
if fail:
self.assertIn(response.status_code, (302, 403),
f'Unexpected status code for user {self.current_user}')
self.assertIn(
response.status_code, (302, 403),
f'Unexpected status code for user {self.current_user}'
)
else:
self.assertEqual(response.status_code, 200,
f'Unexpected status code for user {self.current_user}')
self.assertEqual(
response.status_code, 200, f'Unexpected status code for user {self.current_user}'
)
# Wrong admin password
if not fail:
response = self.client.post(reverse('core:user:delete', kwargs={'user_pk': 2}),
{'password': 'blargh'})
response = self.client.post(
reverse('core:user:delete', kwargs={'user_pk': 2}), {'password': 'blargh'}
)
self.assertEqual(response.status_code, 200)
self.assertEqual(User.objects.filter(username='test').count(), 1)
# Correct user password
response = self.client.post(reverse('core:user:delete', kwargs={'user_pk': 2}),
{'password': self.current_password})
response = self.client.post(
reverse('core:user:delete', kwargs={'user_pk': 2}), {'password': self.current_password}
)
if fail:
self.assertIn(response.status_code, (302, 403))
self.assertEqual(User.objects.filter(username='test').count(), 1)

View File

@@ -34,8 +34,10 @@ class FeedbackTestCase(WgerTestCase):
"""
response = self.client.get(reverse('core:feedback'))
self.assertEqual(response.status_code, 200)
response = self.client.post(reverse('core:feedback'),
{'comment': 'A very long and interesting comment'})
response = self.client.post(
reverse('core:feedback'),
{'comment': 'A very long and interesting comment'},
)
if logged_in:
self.assertEqual(response.status_code, 302)
self.assertEqual(len(mail.outbox), 1)
@@ -52,9 +54,12 @@ class FeedbackTestCase(WgerTestCase):
self.assertEqual(len(mail.outbox), 0)
# Correctly filled in reCaptcha
response = self.client.post(reverse('core:feedback'),
{'comment': 'A very long and interesting comment',
'g-recaptcha-response': 'PASSED'})
response = self.client.post(
reverse('core:feedback'), {
'comment': 'A very long and interesting comment',
'g-recaptcha-response': 'PASSED'
}
)
self.assertEqual(response.status_code, 302)
self.assertEqual(len(mail.outbox), 1)

View File

@@ -12,7 +12,6 @@
#
# You should have received a copy of the GNU Affero General Public License
# Django
from django.urls import reverse
@@ -77,10 +76,11 @@ class DashboardTestCase(WgerTestCase):
#
# 3. Add a weight entry
#
self.client.post(reverse('weight:add'),
{'weight': 100,
'date': '2012-01-01',
'user': 1},)
self.client.post(reverse('weight:add'), {
'weight': 100,
'date': '2012-01-01',
'user': 1,
})
response = self.client.get(reverse('core:dashboard'))
self.assertEqual(response.status_code, 200)

View File

@@ -26,7 +26,7 @@ from wger.core.tests.base_testcase import (
WgerAddTestCase,
WgerDeleteTestCase,
WgerEditTestCase,
WgerTestCase
WgerTestCase,
)
@@ -67,8 +67,7 @@ class CreateLanguageTestCase(WgerAddTestCase):
object_class = Language
url = 'core:language:add'
data = {'short_name': 'dk',
'full_name': 'Dansk'}
data = {'short_name': 'dk', 'full_name': 'Dansk'}
class EditLanguageTestCase(WgerEditTestCase):
@@ -79,8 +78,7 @@ class EditLanguageTestCase(WgerEditTestCase):
object_class = Language
url = 'core:language:edit'
pk = 1
data = {'short_name': 'dk',
'full_name': 'Dansk'}
data = {'short_name': 'dk', 'full_name': 'Dansk'}
class DeleteLanguageTestCase(WgerDeleteTestCase):

View File

@@ -21,7 +21,7 @@ from wger.core.tests.base_testcase import (
WgerAddTestCase,
WgerDeleteTestCase,
WgerEditTestCase,
WgerTestCase
WgerTestCase,
)
@@ -34,8 +34,10 @@ class LicenseRepresentationTestCase(WgerTestCase):
"""
Test that the representation of an object is correct
"""
self.assertEqual(f"{License.objects.get(pk=1)}",
'A cool and free license - Germany (ACAFL - DE)')
self.assertEqual(
f"{License.objects.get(pk=1)}",
'A cool and free license - Germany (ACAFL - DE)',
)
class LicenseOverviewTest(WgerAccessTestCase):
@@ -53,8 +55,7 @@ class AddLicenseTestCase(WgerAddTestCase):
object_class = License
url = 'core:license:add'
data = {'full_name': 'Something here',
'short_name': 'SH'}
data = {'full_name': 'Something here', 'short_name': 'SH'}
class DeleteLicenseTestCase(WgerDeleteTestCase):
@@ -75,8 +76,7 @@ class EditLicenseTestCase(WgerEditTestCase):
object_class = License
url = 'core:license:edit'
pk = 1
data = {'full_name': 'Something here 1.1',
'short_name': 'SH 1.1'}
data = {'full_name': 'Something here 1.1', 'short_name': 'SH 1.1'}
class LicenseApiTestCase(api_base_test.ApiBaseResourceTestCase):

View File

@@ -26,7 +26,6 @@ from wger.core.tests.base_testcase import WgerTestCase
from wger.utils.constants import TWOPLACES
from wger.weight.models import WeightEntry
logger = logging.getLogger(__name__)
@@ -50,19 +49,22 @@ class PreferencesTestCase(WgerTestCase):
self.assertTemplateUsed('preferences.html')
# Change some preferences
response = self.client.post(reverse('core:user:preferences'),
{'show_comments': True,
'show_english_ingredients': True,
'email': 'my-new-email@example.com',
'workout_reminder_active': True,
'workout_reminder': '30',
'workout_duration': 12,
'notification_language': 2,
'timer_active': False,
'timer_pause': 100,
'num_days_weight_reminder': 10,
'weight_unit': 'kg',
'birthdate': '02/25/1987'})
response = self.client.post(
reverse('core:user:preferences'), {
'show_comments': True,
'show_english_ingredients': True,
'email': 'my-new-email@example.com',
'workout_reminder_active': True,
'workout_reminder': '30',
'workout_duration': 12,
'notification_language': 2,
'timer_active': False,
'timer_pause': 100,
'num_days_weight_reminder': 10,
'weight_unit': 'kg',
'birthdate': '02/25/1987',
}
)
self.assertEqual(response.status_code, 302)
response = self.client.get(reverse('core:user:preferences'))
@@ -74,19 +76,22 @@ class PreferencesTestCase(WgerTestCase):
self.assertEqual(User.objects.get(username='test').email, 'my-new-email@example.com')
# Change some preferences
response = self.client.post(reverse('core:user:preferences'),
{'show_comments': False,
'show_english_ingredients': True,
'email': '',
'workout_reminder_active': True,
'workout_reminder': 22,
'workout_duration': 10,
'notification_language': 2,
'timer_active': True,
'timer_pause': 40,
'num_days_weight_reminder': 10,
'weight_unit': 'lb',
'birthdate': '02/25/1987'})
response = self.client.post(
reverse('core:user:preferences'), {
'show_comments': False,
'show_english_ingredients': True,
'email': '',
'workout_reminder_active': True,
'workout_reminder': 22,
'workout_duration': 10,
'notification_language': 2,
'timer_active': True,
'timer_pause': 40,
'num_days_weight_reminder': 10,
'weight_unit': 'lb',
'birthdate': '02/25/1987',
}
)
self.assertEqual(response.status_code, 302)
response = self.client.get(reverse('core:user:preferences'))
@@ -102,19 +107,25 @@ class PreferencesTestCase(WgerTestCase):
# Member2 has a contract
user = User.objects.get(username='member2')
self.assertEqual(user.userprofile.address,
{'phone': '01234-567890',
'zip_code': '00000',
'street': 'Gassenstr. 14',
'city': 'The City'})
self.assertEqual(
user.userprofile.address, {
'phone': '01234-567890',
'zip_code': '00000',
'street': 'Gassenstr. 14',
'city': 'The City',
}
)
# Test has no contracts
user = User.objects.get(username='test')
self.assertEqual(user.userprofile.address,
{'phone': '',
'zip_code': '',
'street': '',
'city': ''})
self.assertEqual(
user.userprofile.address, {
'phone': '',
'zip_code': '',
'street': '',
'city': '',
}
)
class UserBodyweightTestCase(WgerTestCase):
@@ -201,6 +212,7 @@ class PreferencesCalculationsTestCase(WgerTestCase):
"""
Tests the different calculation method in the user profile
"""
def test_last_weight_entry(self):
"""
Tests that the last weight entry is correctly returned
@@ -235,9 +247,11 @@ class PreferencesCalculationsTestCase(WgerTestCase):
user = User.objects.get(pk=2)
bmi = user.userprofile.calculate_bmi()
self.assertEqual(bmi,
user.userprofile.weight.quantize(TWOPLACES)
/ decimal.Decimal(1.80 * 1.80).quantize(TWOPLACES))
self.assertEqual(
bmi,
user.userprofile.weight.quantize(TWOPLACES) /
decimal.Decimal(1.80 * 1.80).quantize(TWOPLACES)
)
def test_basal_metabolic_rate(self):
"""
@@ -269,23 +283,31 @@ class PreferencesCalculationsTestCase(WgerTestCase):
self.user_login('test')
user = User.objects.get(pk=2)
self.assertEqual(user.userprofile.calculate_activities(),
decimal.Decimal(1.57).quantize(TWOPLACES))
self.assertEqual(
user.userprofile.calculate_activities(),
decimal.Decimal(1.57).quantize(TWOPLACES)
)
# Gender has no influence
user.userprofile.gender = "2"
self.assertEqual(user.userprofile.calculate_activities(),
decimal.Decimal(1.57).quantize(TWOPLACES))
self.assertEqual(
user.userprofile.calculate_activities(),
decimal.Decimal(1.57).quantize(TWOPLACES)
)
# Change some of the parameters
user.userprofile.work_intensity = '3'
self.assertEqual(user.userprofile.calculate_activities(),
decimal.Decimal(1.80).quantize(TWOPLACES))
self.assertEqual(
user.userprofile.calculate_activities(),
decimal.Decimal(1.80).quantize(TWOPLACES)
)
user.userprofile.work_intensity = '2'
user.userprofile.sport_intensity = '2'
self.assertEqual(user.userprofile.calculate_activities(),
decimal.Decimal(1.52).quantize(TWOPLACES))
self.assertEqual(
user.userprofile.calculate_activities(),
decimal.Decimal(1.52).quantize(TWOPLACES)
)
# TODO: the user can't delete or create new profiles

View File

@@ -22,11 +22,10 @@ from django.urls import reverse
# wger
from wger.core.forms import (
RegistrationForm,
RegistrationFormNoCaptcha
RegistrationFormNoCaptcha,
)
from wger.core.tests.base_testcase import WgerTestCase
logger = logging.getLogger(__name__)
@@ -40,19 +39,27 @@ class RegistrationTestCase(WgerTestCase):
Tests that the correct form is used depending on global
configuration settings
"""
with self.settings(WGER_SETTINGS={'USE_RECAPTCHA': True,
'REMOVE_WHITESPACE': False,
'ALLOW_REGISTRATION': True,
'ALLOW_GUEST_USERS': True,
'TWITTER': False}):
with self.settings(
WGER_SETTINGS={
'USE_RECAPTCHA': True,
'REMOVE_WHITESPACE': False,
'ALLOW_REGISTRATION': True,
'ALLOW_GUEST_USERS': True,
'TWITTER': False,
}
):
response = self.client.get(reverse('core:user:registration'))
self.assertIsInstance(response.context['form'], RegistrationForm)
with self.settings(WGER_SETTINGS={'USE_RECAPTCHA': False,
'REMOVE_WHITESPACE': False,
'ALLOW_REGISTRATION': True,
'ALLOW_GUEST_USERS': True,
'TWITTER': False}):
with self.settings(
WGER_SETTINGS={
'USE_RECAPTCHA': False,
'REMOVE_WHITESPACE': False,
'ALLOW_REGISTRATION': True,
'ALLOW_GUEST_USERS': True,
'TWITTER': False,
}
):
response = self.client.get(reverse('core:user:registration'))
self.assertIsInstance(response.context['form'], RegistrationFormNoCaptcha)
@@ -63,11 +70,13 @@ class RegistrationTestCase(WgerTestCase):
self.assertEqual(response.status_code, 200)
# Fill in the registration form
registration_data = {'username': 'myusername',
'password1': 'quai8fai7Zae',
'password2': 'quai8fai7Zae',
'email': 'not an email',
'g-recaptcha-response': 'PASSED', }
registration_data = {
'username': 'myusername',
'password1': 'quai8fai7Zae',
'password2': 'quai8fai7Zae',
'email': 'not an email',
'g-recaptcha-response': 'PASSED',
}
count_before = User.objects.count()
# Wrong email
@@ -103,21 +112,27 @@ class RegistrationTestCase(WgerTestCase):
Test that with deactivated registration no users can register
"""
with self.settings(WGER_SETTINGS={'USE_RECAPTCHA': False,
'REMOVE_WHITESPACE': False,
'ALLOW_GUEST_USERS': True,
'ALLOW_REGISTRATION': False}):
with self.settings(
WGER_SETTINGS={
'USE_RECAPTCHA': False,
'REMOVE_WHITESPACE': False,
'ALLOW_GUEST_USERS': True,
'ALLOW_REGISTRATION': False,
}
):
# Fetch the registration page
response = self.client.get(reverse('core:user:registration'))
self.assertEqual(response.status_code, 302)
# Fill in the registration form
registration_data = {'username': 'myusername',
'password1': 'Xee4fuev1ohj',
'password2': 'Xee4fuev1ohj',
'email': 'my.email@example.com',
'g-recaptcha-response': 'PASSED', }
registration_data = {
'username': 'myusername',
'password1': 'Xee4fuev1ohj',
'password2': 'Xee4fuev1ohj',
'email': 'my.email@example.com',
'g-recaptcha-response': 'PASSED',
}
count_before = User.objects.count()
response = self.client.post(reverse('core:user:registration'), registration_data)

View File

@@ -21,7 +21,7 @@ from wger.core.tests.base_testcase import (
WgerAddTestCase,
WgerDeleteTestCase,
WgerEditTestCase,
WgerTestCase
WgerTestCase,
)
@@ -55,14 +55,16 @@ class AddTestCase(WgerAddTestCase):
url = 'core:repetition-unit:add'
data = {'name': 'Furlongs'}
user_success = 'admin',
user_fail = ('general_manager1',
'general_manager2',
'member1',
'member2',
'trainer2',
'trainer3',
'trainer4',
'manager3')
user_fail = (
'general_manager1',
'general_manager2',
'member1',
'member2',
'trainer2',
'trainer3',
'trainer4',
'manager3',
)
class DeleteTestCase(WgerDeleteTestCase):
@@ -74,14 +76,16 @@ class DeleteTestCase(WgerDeleteTestCase):
object_class = RepetitionUnit
url = 'core:repetition-unit:delete'
user_success = 'admin',
user_fail = ('general_manager1',
'general_manager2',
'member1',
'member2',
'trainer2',
'trainer3',
'trainer4',
'manager3')
user_fail = (
'general_manager1',
'general_manager2',
'member1',
'member2',
'trainer2',
'trainer3',
'trainer4',
'manager3',
)
class EditTestCase(WgerEditTestCase):
@@ -94,14 +98,16 @@ class EditTestCase(WgerEditTestCase):
url = 'core:repetition-unit:edit'
data = {'name': 'Furlongs'}
user_success = 'admin',
user_fail = ('general_manager1',
'general_manager2',
'member1',
'member2',
'trainer2',
'trainer3',
'trainer4',
'manager3')
user_fail = (
'general_manager1',
'general_manager2',
'member1',
'member2',
'trainer2',
'trainer3',
'trainer4',
'manager3',
)
class ApiTestCase(api_base_test.ApiBaseResourceTestCase):

View File

@@ -29,5 +29,4 @@ class RobotsTxtTestCase(WgerTestCase):
response = self.client.get(reverse('robots'))
for lang in Language.objects.all():
self.assertTrue(f'wger.de/{lang.short_name}/sitemap.xml'
in str(response.content))
self.assertTrue(f'wger.de/{lang.short_name}/sitemap.xml' in str(response.content))

View File

@@ -32,14 +32,16 @@ class SitemapTestCase(WgerTestCase):
def test_sitemap_exercises(self):
response = self.client.get(reverse('django.contrib.sitemaps.views.sitemap',
kwargs={'section': 'exercises'}))
response = self.client.get(
reverse('django.contrib.sitemaps.views.sitemap', kwargs={'section': 'exercises'})
)
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.context['urlset']), 9)
def test_sitemap_ingredients(self):
response = self.client.get(reverse('django.contrib.sitemaps.views.sitemap',
kwargs={'section': 'nutrition'}))
response = self.client.get(
reverse('django.contrib.sitemaps.views.sitemap', kwargs={'section': 'nutrition'})
)
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.context['urlset']), 13)

View File

@@ -18,13 +18,12 @@ import logging
# Django
from django.template import (
Context,
Template
Template,
)
# wger
from wger.core.tests.base_testcase import WgerTestCase
logger = logging.getLogger(__name__)
@@ -37,8 +36,10 @@ class SpacelessTestCase(WgerTestCase):
"""
Tests the custom spaceless template tag
"""
t = Template('{% load wger_extras %}'
'{% spaceless_config %}<p>A text</p> <p>more</p>{% endspaceless_config %}')
t = Template(
'{% load wger_extras %}'
'{% spaceless_config %}<p>A text</p> <p>more</p>{% endspaceless_config %}'
)
context = Context()
with self.settings(WGER_SETTINGS={'REMOVE_WHITESPACE': True}):

View File

@@ -24,7 +24,7 @@ from django.urls import reverse
# wger
from wger.core.demo import (
create_demo_entries,
create_temporary_user
create_temporary_user,
)
from wger.core.tests.base_testcase import WgerTestCase
from wger.manager.models import (
@@ -32,11 +32,11 @@ from wger.manager.models import (
Schedule,
ScheduleStep,
Workout,
WorkoutLog
WorkoutLog,
)
from wger.nutrition.models import (
Meal,
NutritionPlan
NutritionPlan,
)
from wger.weight.models import WeightEntry
@@ -58,11 +58,15 @@ class DemoUserTestCase(WgerTestCase):
Tests that the helper function creates demo data (workout, etc.)
for the demo users
"""
with self.settings(WGER_SETTINGS={'USE_RECAPTCHA': True,
'REMOVE_WHITESPACE': False,
'ALLOW_REGISTRATION': True,
'ALLOW_GUEST_USERS': False,
'TWITTER': False}):
with self.settings(
WGER_SETTINGS={
'USE_RECAPTCHA': True,
'REMOVE_WHITESPACE': False,
'ALLOW_REGISTRATION': True,
'ALLOW_GUEST_USERS': False,
'TWITTER': False,
}
):
self.assertEqual(self.count_temp_users(), 1)
self.client.get(reverse('core:dashboard'))
self.assertEqual(self.count_temp_users(), 1)
@@ -109,9 +113,9 @@ class DemoUserTestCase(WgerTestCase):
temp = []
for i in range(1, 5):
creation_date = datetime.date.today() - datetime.timedelta(days=i)
entry = WeightEntry(user=user,
weight=80 + 0.5 * i + random.randint(1, 3),
date=creation_date)
entry = WeightEntry(
user=user, weight=80 + 0.5 * i + random.randint(1, 3), date=creation_date
)
temp.append(entry)
WeightEntry.objects.bulk_create(temp)
create_demo_entries(user)
@@ -168,13 +172,12 @@ class DemoUserTestCase(WgerTestCase):
demo_notice_text = 'You are using a guest account'
self.user_login('demo')
self.assertContains(self.client.get(reverse('core:dashboard')), demo_notice_text)
self.assertContains(self.client.get(reverse('manager:workout:overview')),
demo_notice_text)
self.assertContains(self.client.get(reverse('exercise:exercise:overview')),
demo_notice_text)
self.assertContains(self.client.get(reverse('manager:workout:overview')), demo_notice_text)
self.assertContains(
self.client.get(reverse('exercise:exercise:overview')), demo_notice_text
)
self.assertContains(self.client.get(reverse('exercise:muscle:overview')), demo_notice_text)
self.assertContains(self.client.get(reverse('nutrition:plan:overview')),
demo_notice_text)
self.assertContains(self.client.get(reverse('nutrition:plan:overview')), demo_notice_text)
self.assertContains(self.client.get(reverse('software:issues')), demo_notice_text)
self.assertContains(self.client.get(reverse('software:license')), demo_notice_text)

View File

@@ -16,14 +16,14 @@
from django.contrib.auth.models import User
from django.urls import (
reverse,
reverse_lazy
reverse_lazy,
)
# wger
from wger.core.tests.base_testcase import (
WgerAccessTestCase,
WgerEditTestCase,
WgerTestCase
WgerTestCase,
)
@@ -32,20 +32,24 @@ class StatusUserTestCase(WgerTestCase):
Test activating and deactivating users
"""
user_success = ('general_manager1',
'general_manager2',
'manager1',
'manager2',
'trainer1',
'trainer2',
'trainer3')
user_success = (
'general_manager1',
'general_manager2',
'manager1',
'manager2',
'trainer1',
'trainer2',
'trainer3',
)
user_fail = ('member1',
'member2',
'member3',
'member4',
'manager3',
'trainer4')
user_fail = (
'member1',
'member2',
'member3',
'member4',
'manager3',
'trainer4',
)
def activate(self, fail=False):
"""
@@ -140,20 +144,26 @@ class EditUserTestCase(WgerEditTestCase):
object_class = User
url = 'core:user:edit'
pk = 2
data = {'email': 'another.email@example.com',
'first_name': 'Name',
'last_name': 'Last name'}
user_success = ('admin',
'general_manager1',
'general_manager2',
'manager1',
'manager2')
user_fail = ('member1',
'member2',
'manager3',
'trainer2',
'trainer3',
'trainer4')
data = {
'email': 'another.email@example.com',
'first_name': 'Name',
'last_name': 'Last name',
}
user_success = (
'admin',
'general_manager1',
'general_manager2',
'manager1',
'manager2',
)
user_fail = (
'member1',
'member2',
'manager3',
'trainer2',
'trainer3',
'trainer4',
)
class EditUserTestCase2(WgerEditTestCase):
@@ -164,18 +174,24 @@ class EditUserTestCase2(WgerEditTestCase):
object_class = User
url = 'core:user:edit'
pk = 19
data = {'email': 'another.email@example.com',
'first_name': 'Name',
'last_name': 'Last name'}
user_success = ('admin',
'general_manager1',
'general_manager2',
'manager3')
user_fail = ('member1',
'member2',
'trainer2',
'trainer3',
'trainer4')
data = {
'email': 'another.email@example.com',
'first_name': 'Name',
'last_name': 'Last name',
}
user_success = (
'admin',
'general_manager1',
'general_manager2',
'manager3',
)
user_fail = (
'member1',
'member2',
'trainer2',
'trainer3',
'trainer4',
)
class UserListTestCase(WgerAccessTestCase):
@@ -184,17 +200,21 @@ class UserListTestCase(WgerAccessTestCase):
"""
url = 'core:user:list'
user_success = ('admin',
'general_manager1',
'general_manager2')
user_fail = ('member1',
'member2',
'manager1',
'manager2',
'manager3',
'trainer2',
'trainer3',
'trainer4')
user_success = (
'admin',
'general_manager1',
'general_manager2',
)
user_fail = (
'member1',
'member2',
'manager1',
'manager2',
'manager3',
'trainer2',
'trainer3',
'trainer4',
)
class UserDetailPageTestCase(WgerAccessTestCase):
@@ -203,16 +223,20 @@ class UserDetailPageTestCase(WgerAccessTestCase):
"""
url = reverse_lazy('core:user:overview', kwargs={'pk': 2})
user_success = ('trainer1',
'trainer2',
'manager1',
'general_manager1',
'general_manager2')
user_fail = ('trainer4',
'trainer5',
'manager3',
'member1',
'member2')
user_success = (
'trainer1',
'trainer2',
'manager1',
'general_manager1',
'general_manager2',
)
user_fail = (
'trainer4',
'trainer5',
'manager3',
'member1',
'member2',
)
class UserDetailPageTestCase2(WgerAccessTestCase):
@@ -221,13 +245,17 @@ class UserDetailPageTestCase2(WgerAccessTestCase):
"""
url = reverse_lazy('core:user:overview', kwargs={'pk': 19})
user_success = ('trainer4',
'trainer5',
'manager3',
'general_manager1',
'general_manager2')
user_fail = ('trainer1',
'trainer2',
'manager1',
'member1',
'member2')
user_success = (
'trainer4',
'trainer5',
'manager3',
'general_manager1',
'general_manager2',
)
user_fail = (
'trainer1',
'trainer2',
'manager1',
'member1',
'member2',
)

View File

@@ -11,7 +11,7 @@ from rest_framework.authtoken.models import Token
from rest_framework.status import (
HTTP_201_CREATED,
HTTP_400_BAD_REQUEST,
HTTP_403_FORBIDDEN
HTTP_403_FORBIDDEN,
)
# wger
@@ -43,8 +43,10 @@ class CreateUserCommand(WgerTestCase):
self.assertTrue(user.userprofile.can_add_user)
call_command('add-user-rest', 'test', disable=True, stdout=self.out, no_color=True)
self.assertEqual('test is now DISABLED from adding users via the API\n',
self.out.getvalue())
self.assertEqual(
'test is now DISABLED from adding users via the API\n',
self.out.getvalue(),
)
user = User.objects.get(username='test')
self.assertFalse(user.userprofile.can_add_user)
@@ -65,11 +67,14 @@ class CreateUserCommand(WgerTestCase):
token = Token.objects.get(user=user)
count_before = User.objects.count()
response = self.client.post(reverse('api_register'),
{'username': 'restapi',
'email': 'abc@cde.fg',
'password': 'AekaiLe0ga'},
Authorization=f'Token {token.key}')
response = self.client.post(
reverse('api_register'), {
'username': 'restapi',
'email': 'abc@cde.fg',
'password': 'AekaiLe0ga'
},
Authorization=f'Token {token.key}'
)
count_after = User.objects.count()
self.assertEqual(response.status_code, HTTP_201_CREATED)
@@ -87,10 +92,13 @@ class CreateUserCommand(WgerTestCase):
token = Token.objects.get(user=user)
count_before = User.objects.count()
response = self.client.post(reverse('api_register'),
{'username': 'restapi',
'password': 'AekaiLe0ga'},
Authorization=f'Token {token.key}')
response = self.client.post(
reverse('api_register'), {
'username': 'restapi',
'password': 'AekaiLe0ga'
},
Authorization=f'Token {token.key}'
)
count_after = User.objects.count()
self.assertEqual(response.status_code, HTTP_201_CREATED)
@@ -106,10 +114,13 @@ class CreateUserCommand(WgerTestCase):
self.user_login('admin')
count_before = User.objects.count()
response = self.client.post(reverse('api_register'),
{'username': 'restapi',
'email': 'abc@cde.fg',
'password': 'AekaiLe0ga'})
response = self.client.post(
reverse('api_register'), {
'username': 'restapi',
'email': 'abc@cde.fg',
'password': 'AekaiLe0ga'
}
)
count_after = User.objects.count()
self.assertEqual(response.status_code, HTTP_403_FORBIDDEN)
@@ -122,9 +133,11 @@ class CreateUserCommand(WgerTestCase):
user = User.objects.get(username='test')
token = Token.objects.get(user=user)
response = self.client.post(reverse('api_register'),
{'password': 'AekaiLe0ga'},
Authorization=f'Token {token.key}')
response = self.client.post(
reverse('api_register'),
{'password': 'AekaiLe0ga'},
Authorization=f'Token {token.key}',
)
self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST)
@@ -135,11 +148,14 @@ class CreateUserCommand(WgerTestCase):
user = User.objects.get(username='test')
token = Token.objects.get(user=user)
response = self.client.post(reverse('api_register'),
{'username': 'restapi',
'email': 'example.com',
'password': 'AekaiLe0ga'},
Authorization=f'Token {token.key}')
response = self.client.post(
reverse('api_register'), {
'username': 'restapi',
'email': 'example.com',
'password': 'AekaiLe0ga'
},
Authorization=f'Token {token.key}'
)
self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST)
@@ -150,10 +166,13 @@ class CreateUserCommand(WgerTestCase):
user = User.objects.get(username='test')
token = Token.objects.get(user=user)
response = self.client.post(reverse('api_register'),
{'username': 'restapi',
'email': 'admin@example.com',
'password': 'AekaiLe0ga'},
Authorization=f'Token {token.key}')
response = self.client.post(
reverse('api_register'), {
'username': 'restapi',
'email': 'admin@example.com',
'password': 'AekaiLe0ga'
},
Authorization=f'Token {token.key}'
)
self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST)

View File

@@ -21,7 +21,7 @@ from wger.core.tests.base_testcase import (
WgerAddTestCase,
WgerDeleteTestCase,
WgerEditTestCase,
WgerTestCase
WgerTestCase,
)
@@ -55,14 +55,16 @@ class AddTestCase(WgerAddTestCase):
url = 'core:weight-unit:add'
data = {'name': 'Furlongs'}
user_success = 'admin',
user_fail = ('general_manager1',
'general_manager2',
'member1',
'member2',
'trainer2',
'trainer3',
'trainer4',
'manager3')
user_fail = (
'general_manager1',
'general_manager2',
'member1',
'member2',
'trainer2',
'trainer3',
'trainer4',
'manager3',
)
class DeleteTestCase(WgerDeleteTestCase):
@@ -74,14 +76,16 @@ class DeleteTestCase(WgerDeleteTestCase):
object_class = WeightUnit
url = 'core:weight-unit:delete'
user_success = 'admin',
user_fail = ('general_manager1',
'general_manager2',
'member1',
'member2',
'trainer2',
'trainer3',
'trainer4',
'manager3')
user_fail = (
'general_manager1',
'general_manager2',
'member1',
'member2',
'trainer2',
'trainer3',
'trainer4',
'manager3',
)
class EditTestCase(WgerEditTestCase):
@@ -94,14 +98,16 @@ class EditTestCase(WgerEditTestCase):
url = 'core:weight-unit:edit'
data = {'name': 'Furlongs'}
user_success = 'admin',
user_fail = ('general_manager1',
'general_manager2',
'member1',
'member2',
'trainer2',
'trainer3',
'trainer4',
'manager3')
user_fail = (
'general_manager1',
'general_manager2',
'member1',
'member2',
'trainer2',
'trainer3',
'trainer4',
'manager3',
)
class ApiTestCase(api_base_test.ApiBaseResourceTestCase):

View File

@@ -14,11 +14,10 @@
#
# You should have received a copy of the GNU Affero General Public License
# Django
from django.conf.urls import (
include,
url
url,
)
from django.contrib.auth import views
from django.urls import path
@@ -32,173 +31,245 @@ from wger.core.views import (
misc,
repetition_units,
user,
weight_units
weight_units,
)
# sub patterns for languages
patterns_language = [
path('list',
languages.LanguageListView.as_view(),
name='overview'),
path('<int:pk>/view',
languages.LanguageDetailView.as_view(),
name='view'),
path('<int:pk>/delete',
languages.LanguageDeleteView.as_view(),
name='delete'),
path('<int:pk>/edit',
languages.LanguageEditView.as_view(),
name='edit'),
path('add',
languages.LanguageCreateView.as_view(),
name='add'),
path(
'list',
languages.LanguageListView.as_view(),
name='overview',
),
path(
'<int:pk>/view',
languages.LanguageDetailView.as_view(),
name='view',
),
path(
'<int:pk>/delete',
languages.LanguageDeleteView.as_view(),
name='delete',
),
path(
'<int:pk>/edit',
languages.LanguageEditView.as_view(),
name='edit',
),
path(
'add',
languages.LanguageCreateView.as_view(),
name='add',
),
]
# sub patterns for user
patterns_user = [
path('login',
views.LoginView.as_view(template_name='user/login.html',
authentication_form=UserLoginForm),
name='login'),
path('logout',
user.logout,
name='logout'),
path('delete',
user.delete,
name='delete'),
path('<int:user_pk>/delete',
user.delete,
name='delete'),
path('<int:user_pk>/trainer-login',
user.trainer_login,
name='trainer-login'),
path('registration',
user.registration,
name='registration'),
path('preferences',
user.preferences,
name='preferences'),
path('api-key',
user.api_key,
name='api-key'),
path('demo-entries',
misc.demo_entries,
name='demo-entries'),
path('<int:pk>/activate',
user.UserActivateView.as_view(),
name='activate'),
path('<int:pk>/deactivate',
user.UserDeactivateView.as_view(),
name='deactivate'),
path('<int:pk>/edit',
user.UserEditView.as_view(),
name='edit'),
path('<int:pk>/overview',
user.UserDetailView.as_view(),
name='overview'),
path('list',
user.UserListView.as_view(),
name='list'),
path(
'login',
views.LoginView.as_view(template_name='user/login.html', authentication_form=UserLoginForm),
name='login',
),
path(
'logout',
user.logout,
name='logout',
),
path(
'delete',
user.delete,
name='delete',
),
path(
'<int:user_pk>/delete',
user.delete,
name='delete',
),
path(
'<int:user_pk>/trainer-login',
user.trainer_login,
name='trainer-login',
),
path(
'registration',
user.registration,
name='registration',
),
path(
'preferences',
user.preferences,
name='preferences',
),
path(
'api-key',
user.api_key,
name='api-key',
),
path(
'demo-entries',
misc.demo_entries,
name='demo-entries',
),
path(
'<int:pk>/activate',
user.UserActivateView.as_view(),
name='activate',
),
path(
'<int:pk>/deactivate',
user.UserDeactivateView.as_view(),
name='deactivate',
),
path(
'<int:pk>/edit',
user.UserEditView.as_view(),
name='edit',
),
path(
'<int:pk>/overview',
user.UserDetailView.as_view(),
name='overview',
),
path(
'list',
user.UserListView.as_view(),
name='list',
),
# Password reset is implemented by Django, no need to cook our own soup here
# (besides the templates)
path('password/change',
user.WgerPasswordChangeView.as_view(),
name='change-password'),
path('password/reset/',
user.WgerPasswordResetView.as_view(),
name='password_reset'),
path('password/reset/done/',
views.PasswordResetDoneView.as_view(),
name='password_reset_done'),
url(r'^password/reset/check/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,33})$',
path(
'password/change',
user.WgerPasswordChangeView.as_view(),
name='change-password',
),
path(
'password/reset/',
user.WgerPasswordResetView.as_view(),
name='password_reset',
),
path(
'password/reset/done/',
views.PasswordResetDoneView.as_view(),
name='password_reset_done',
),
url(
r'^password/reset/check/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,33})$',
user.WgerPasswordResetConfirmView.as_view(),
name='password_reset_confirm'),
path('password/reset/complete/',
views.PasswordResetCompleteView.as_view(),
name='password_reset_complete'),
name='password_reset_confirm',
),
path(
'password/reset/complete/',
views.PasswordResetCompleteView.as_view(),
name='password_reset_complete',
),
]
# sub patterns for licenses
patterns_license = [
path('license/list',
license.LicenseListView.as_view(),
name='list'),
path('license/add',
license.LicenseAddView.as_view(),
name='add'),
path('license/<int:pk>/edit',
license.LicenseUpdateView.as_view(),
name='edit'),
path('license/<int:pk>/delete',
license.LicenseDeleteView.as_view(),
name='delete'),
path(
'license/list',
license.LicenseListView.as_view(),
name='list',
),
path(
'license/add',
license.LicenseAddView.as_view(),
name='add',
),
path(
'license/<int:pk>/edit',
license.LicenseUpdateView.as_view(),
name='edit',
),
path(
'license/<int:pk>/delete',
license.LicenseDeleteView.as_view(),
name='delete',
),
]
# sub patterns for setting units
patterns_repetition_units = [
path('list',
repetition_units.ListView.as_view(),
name='list'),
path('add',
repetition_units.AddView.as_view(),
name='add'),
path('<int:pk>/edit',
repetition_units.UpdateView.as_view(),
name='edit'),
path('<int:pk>/delete',
repetition_units.DeleteView.as_view(),
name='delete'),
path(
'list',
repetition_units.ListView.as_view(),
name='list',
),
path(
'add',
repetition_units.AddView.as_view(),
name='add',
),
path(
'<int:pk>/edit',
repetition_units.UpdateView.as_view(),
name='edit',
),
path(
'<int:pk>/delete',
repetition_units.DeleteView.as_view(),
name='delete',
),
]
# sub patterns for setting units
patterns_weight_units = [
path('list',
weight_units.ListView.as_view(),
name='list'),
path('add',
weight_units.AddView.as_view(),
name='add'),
path('<int:pk>)/edit',
weight_units.UpdateView.as_view(),
name='edit'),
path('<int:pk>/delete',
weight_units.DeleteView.as_view(),
name='delete'),
path(
'list',
weight_units.ListView.as_view(),
name='list',
),
path(
'add',
weight_units.AddView.as_view(),
name='add',
),
path(
'<int:pk>)/edit',
weight_units.UpdateView.as_view(),
name='edit',
),
path(
'<int:pk>/delete',
weight_units.DeleteView.as_view(),
name='delete',
),
]
#
# Actual patterns
#
urlpatterns = [
# The landing page
path('',
misc.index,
name='index'),
path('', misc.index, name='index'),
# The dashboard
path('dashboard',
misc.dashboard,
name='dashboard'),
path('dashboard', misc.dashboard, name='dashboard'),
# Others
path('about',
TemplateView.as_view(template_name="misc/about.html"),
name='about'),
path('contact',
misc.ContactClassView.as_view(template_name="misc/contact.html"),
name='contact'),
path('feedback',
misc.FeedbackClass.as_view(),
name='feedback'),
path(
'about',
TemplateView.as_view(template_name="misc/about.html"),
name='about',
),
path(
'contact',
misc.ContactClassView.as_view(template_name="misc/contact.html"),
name='contact',
),
path(
'feedback',
misc.FeedbackClass.as_view(),
name='feedback',
),
path('language/', include((patterns_language, 'language'), namespace="language")),
path('user/', include((patterns_user, 'user'), namespace="user")),
path('license/', include((patterns_license, 'license'), namespace="license")),
path('repetition-unit/', include((patterns_repetition_units, 'repetition-unit'), namespace="repetition-unit")),
path(
'repetition-unit/',
include((patterns_repetition_units, 'repetition-unit'), namespace="repetition-unit")
),
path('weight-unit/', include((patterns_weight_units, 'weight-unit'), namespace="weight-unit")),
]

View File

@@ -20,29 +20,28 @@ import logging
# Django
from django.contrib.auth.mixins import (
LoginRequiredMixin,
PermissionRequiredMixin
PermissionRequiredMixin,
)
from django.urls import reverse_lazy
from django.utils.translation import (
gettext as _,
gettext_lazy
gettext_lazy,
)
from django.views.generic import (
CreateView,
DeleteView,
DetailView,
ListView,
UpdateView
UpdateView,
)
# wger
from wger.core.models import Language
from wger.utils.generic_views import (
WgerDeleteMixin,
WgerFormMixin
WgerFormMixin,
)
logger = logging.getLogger(__name__)

View File

@@ -20,28 +20,27 @@ import logging
# Django
from django.contrib.auth.mixins import (
LoginRequiredMixin,
PermissionRequiredMixin
PermissionRequiredMixin,
)
from django.urls import reverse_lazy
from django.utils.translation import (
gettext as _,
gettext_lazy
gettext_lazy,
)
from django.views.generic import (
CreateView,
DeleteView,
ListView,
UpdateView
UpdateView,
)
# wger
from wger.core.models import License
from wger.utils.generic_views import (
WgerDeleteMixin,
WgerFormMixin
WgerFormMixin,
)
logger = logging.getLogger(__name__)

View File

@@ -29,7 +29,7 @@ from django.shortcuts import render
from django.template.loader import render_to_string
from django.urls import (
reverse,
reverse_lazy
reverse_lazy,
)
from django.utils.text import slugify
from django.utils.translation import gettext as _
@@ -43,11 +43,11 @@ from crispy_forms.layout import Submit
# wger
from wger.core.demo import (
create_demo_entries,
create_temporary_user
create_temporary_user,
)
from wger.core.forms import (
FeedbackAnonymousForm,
FeedbackRegisteredForm
FeedbackRegisteredForm,
)
from wger.core.models import DaysOfWeek
from wger.manager.models import Schedule
@@ -55,7 +55,6 @@ from wger.nutrition.models import NutritionPlan
from wger.weight.helpers import get_last_entries
from wger.weight.models import WeightEntry
logger = logging.getLogger(__name__)
@@ -79,8 +78,12 @@ def demo_entries(request):
if not settings.WGER_SETTINGS['ALLOW_GUEST_USERS']:
return HttpResponseRedirect(reverse('software:features'))
if (((not request.user.is_authenticated or request.user.userprofile.is_temporary)
and not request.session['has_demo_data'])):
if (
(
(not request.user.is_authenticated or request.user.userprofile.is_temporary)
and not request.session['has_demo_data']
)
):
# If we reach this from a page that has no user created by the
# middleware, do that now
if not request.user.is_authenticated:
@@ -90,10 +93,15 @@ def demo_entries(request):
# OK, continue
create_demo_entries(request.user)
request.session['has_demo_data'] = True
messages.success(request, _('We have created sample workout, workout schedules, weight '
'logs, (body) weight and nutrition plan entries so you can '
'better see what this site can do. Feel free to edit or '
'delete them!'))
messages.success(
request,
_(
'We have created sample workout, workout schedules, weight '
'logs, (body) weight and nutrition plan entries so you can '
'better see what this site can do. Feel free to edit or '
'delete them!'
)
)
return HttpResponseRedirect(reverse('core:dashboard'))
@@ -156,12 +164,17 @@ def dashboard(request):
class ContactClassView(TemplateView):
def get_context_data(self, **kwargs):
context = super(ContactClassView, self).get_context_data(**kwargs)
context.update({'discord': 'https://discord.gg/rPWFv6W',
'contribute': reverse('software:contribute'),
'issues': reverse('software:issues'),
'feedback': reverse('core:feedback')})
context.update(
{
'discord': 'https://discord.gg/rPWFv6W',
'contribute': reverse('software:contribute'),
'issues': reverse('software:issues'),
'feedback': reverse('core:feedback')
}
)
return context

View File

@@ -20,29 +20,28 @@ import logging
# Django
from django.contrib.auth.mixins import (
LoginRequiredMixin,
PermissionRequiredMixin
PermissionRequiredMixin,
)
from django.http import HttpResponseForbidden
from django.urls import reverse_lazy
from django.utils.translation import (
gettext as _,
gettext_lazy
gettext_lazy,
)
from django.views.generic import (
CreateView,
DeleteView,
ListView,
UpdateView
UpdateView,
)
# wger
from wger.core.models import RepetitionUnit
from wger.utils.generic_views import (
WgerDeleteMixin,
WgerFormMixin
WgerFormMixin,
)
logger = logging.getLogger(__name__)

View File

@@ -23,44 +23,44 @@ from django.contrib import messages
from django.contrib.auth import (
authenticate,
login as django_login,
logout as django_logout
logout as django_logout,
)
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import (
LoginRequiredMixin,
PermissionRequiredMixin
PermissionRequiredMixin,
)
from django.contrib.auth.models import User
from django.contrib.auth.views import (
LoginView,
PasswordChangeView,
PasswordResetConfirmView,
PasswordResetView
PasswordResetView,
)
from django.http import (
HttpResponseForbidden,
HttpResponseNotFound,
HttpResponseRedirect
HttpResponseRedirect,
)
from django.shortcuts import (
get_object_or_404,
render
render,
)
from django.template.context_processors import csrf
from django.urls import (
reverse,
reverse_lazy
reverse_lazy,
)
from django.utils import translation
from django.utils.translation import (
gettext as _,
gettext_lazy
gettext_lazy,
)
from django.views.generic import (
DetailView,
ListView,
RedirectView,
UpdateView
UpdateView,
)
# Third Party
@@ -70,7 +70,7 @@ from crispy_forms.layout import (
Column,
Layout,
Row,
Submit
Submit,
)
from rest_framework.authtoken.models import Token
@@ -82,28 +82,27 @@ from wger.core.forms import (
RegistrationFormNoCaptcha,
UserLoginForm,
UserPersonalInformationForm,
UserPreferencesForm
UserPreferencesForm,
)
from wger.core.models import Language
from wger.gym.models import (
AdminUserNote,
Contract,
GymUserConfig
GymUserConfig,
)
from wger.manager.models import (
Workout,
WorkoutLog,
WorkoutSession
WorkoutSession,
)
from wger.nutrition.models import NutritionPlan
from wger.utils.api_token import create_token
from wger.utils.generic_views import (
WgerFormMixin,
WgerMultiplePermissionRequiredMixin
WgerMultiplePermissionRequiredMixin,
)
from wger.weight.models import WeightEntry
logger = logging.getLogger(__name__)
@@ -117,8 +116,7 @@ def login(request):
form = UserLoginForm
form.helper.form_action = reverse('core:user:login') + next_url
return LoginView.as_view(template_name='user/login.html',
authentication_form=form)
return LoginView.as_view(template_name='user/login.html', authentication_form=form)
@login_required()
@@ -152,8 +150,10 @@ def delete(request, user_pk=None):
if form.is_valid():
user.delete()
messages.success(request,
_('Account "{0}" was successfully deleted').format(user.username))
messages.success(
request,
_('Account "{0}" was successfully deleted').format(user.username)
)
if not user_pk:
django_logout(request)
@@ -162,8 +162,7 @@ def delete(request, user_pk=None):
gym_pk = request.user.userprofile.gym_id
return HttpResponseRedirect(reverse('gym:gym:user-list', kwargs={'pk': gym_pk}))
form.helper.form_action = request.path
context = {'form': form,
'user_delete': user}
context = {'form': form, 'user_delete': user}
return render(request, 'user/delete_account.html', context)
@@ -192,20 +191,23 @@ def trainer_login(request, user_pk):
if request.user.userprofile.gym != user.userprofile.gym:
return HttpResponseNotFound(
'There are no users in gym "{}" with user ID "{}".'.format(
request.user.userprofile.gym, user_pk,
))
request.user.userprofile.gym,
user_pk,
)
)
# Check if we're switching back to our original account
own = False
if (user.has_perm('gym.gym_trainer')
or user.has_perm('gym.manage_gym')
or user.has_perm('gym.manage_gyms')):
if (
user.has_perm('gym.gym_trainer') or user.has_perm('gym.manage_gym')
or user.has_perm('gym.manage_gyms')
):
own = True
# Note: when logging without authenticating, it is necessary to set the
# authentication backend
if own:
del(request.session['trainer.identity'])
del (request.session['trainer.identity'])
django_login(request, user, 'django.contrib.auth.backends.ModelBackend')
if not own:
@@ -215,8 +217,9 @@ def trainer_login(request, user_pk):
else:
return HttpResponseRedirect(reverse('core:index'))
else:
return HttpResponseRedirect(reverse('gym:gym:user-list',
kwargs={'pk': user.userprofile.gym_id}))
return HttpResponseRedirect(
reverse('gym:gym:user-list', kwargs={'pk': user.userprofile.gym_id})
)
def logout(request):
@@ -258,9 +261,7 @@ def registration(request):
username = form.cleaned_data['username']
password = form.cleaned_data['password1']
email = form.cleaned_data['email']
user = User.objects.create_user(username,
email,
password)
user = User.objects.create_user(username, email, password)
user.save()
# Pre-set some values of the user's profile
@@ -313,9 +314,11 @@ def preferences(request):
form.save()
redirect = True
else:
data = {'first_name': request.user.first_name,
'last_name': request.user.last_name,
'email': request.user.email}
data = {
'first_name': request.user.first_name,
'last_name': request.user.last_name,
'email': request.user.email
}
form = UserPreferencesForm(initial=data, instance=request.user.userprofile)
@@ -338,9 +341,11 @@ def preferences(request):
return render(request, 'user/preferences.html', template_data)
class UserDeactivateView(LoginRequiredMixin,
WgerMultiplePermissionRequiredMixin,
RedirectView):
class UserDeactivateView(
LoginRequiredMixin,
WgerMultiplePermissionRequiredMixin,
RedirectView,
):
"""
Deactivates a user
"""
@@ -371,9 +376,11 @@ class UserDeactivateView(LoginRequiredMixin,
return reverse('core:user:overview', kwargs=({'pk': pk}))
class UserActivateView(LoginRequiredMixin,
WgerMultiplePermissionRequiredMixin,
RedirectView):
class UserActivateView(
LoginRequiredMixin,
WgerMultiplePermissionRequiredMixin,
RedirectView,
):
"""
Activates a previously deactivated user
"""
@@ -404,10 +411,12 @@ class UserActivateView(LoginRequiredMixin,
return reverse('core:user:overview', kwargs=({'pk': pk}))
class UserEditView(WgerFormMixin,
LoginRequiredMixin,
WgerMultiplePermissionRequiredMixin,
UpdateView):
class UserEditView(
WgerFormMixin,
LoginRequiredMixin,
WgerMultiplePermissionRequiredMixin,
UpdateView,
):
"""
View to update the personal information of an user by an admin
"""
@@ -509,9 +518,13 @@ class UserDetailView(LoginRequiredMixin, WgerMultiplePermissionRequiredMixin, De
workouts = Workout.objects.filter(user=self.object).all()
for workout in workouts:
logs = WorkoutLog.objects.filter(workout=workout)
out.append({'workout': workout,
'logs': logs.dates('date', 'day').count(),
'last_log': logs.last()})
out.append(
{
'workout': workout,
'logs': logs.dates('date', 'day').count(),
'last_log': logs.last()
}
)
context['workouts'] = out
context['weight_entries'] = WeightEntry.objects.filter(user=self.object)\
.order_by('-date')[:5]
@@ -534,19 +547,17 @@ class UserListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
Overview of all users in the instance
"""
model = User
permission_required = ('gym.manage_gyms',)
permission_required = ('gym.manage_gyms', )
template_name = 'user/list.html'
def get_queryset(self):
"""
Return a list with the users, not really a queryset.
"""
out = {'admins': [],
'members': []}
out = {'admins': [], 'members': []}
for u in User.objects.select_related('usercache', 'userprofile__gym').all():
out['members'].append({'obj': u,
'last_log': u.usercache.last_activity})
out['members'].append({'obj': u, 'last_log': u.usercache.last_activity})
return out
@@ -556,12 +567,16 @@ class UserListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
"""
context = super(UserListView, self).get_context_data(**kwargs)
context['show_gym'] = True
context['user_table'] = {'keys': [_('ID'),
_('Username'),
_('Name'),
_('Last activity'),
_('Gym')],
'users': context['object_list']['members']}
context['user_table'] = {
'keys': [
_('ID'),
_('Username'),
_('Name'),
_('Last activity'),
_('Gym'),
],
'users': context['object_list']['members']
}
return context
@@ -580,8 +595,7 @@ class WgerPasswordChangeView(PasswordChangeView):
Column('new_password1', css_class='form-group col-6 mb-0'),
Column('new_password2', css_class='form-group col-6 mb-0'),
css_class='form-row'
),
ButtonHolder(Submit('submit', _("Save"), css_class='btn-success btn-block'))
), ButtonHolder(Submit('submit', _("Save"), css_class='btn-success btn-block'))
)
return form

View File

@@ -20,29 +20,28 @@ import logging
# Django
from django.contrib.auth.mixins import (
LoginRequiredMixin,
PermissionRequiredMixin
PermissionRequiredMixin,
)
from django.http import HttpResponseForbidden
from django.urls import reverse_lazy
from django.utils.translation import (
gettext as _,
gettext_lazy
gettext_lazy,
)
from django.views.generic import (
CreateView,
DeleteView,
ListView,
UpdateView
UpdateView,
)
# wger
from wger.core.models import WeightUnit
from wger.utils.generic_views import (
WgerDeleteMixin,
WgerFormMixin
WgerFormMixin,
)
logger = logging.getLogger(__name__)

View File

@@ -15,10 +15,8 @@
# You should have received a copy of the GNU Affero General Public License
# along with Workout Manager. If not, see <http://www.gnu.org/licenses/>.
# wger
from wger import get_version
VERSION = get_version()
default_app_config = 'wger.exercises.apps.ExerciseConfig'

View File

@@ -21,7 +21,7 @@ from wger.exercises.models import (
Exercise,
ExerciseCategory,
ExerciseComment,
Muscle
Muscle,
)

View File

@@ -25,7 +25,7 @@ from wger.exercises.models import (
ExerciseCategory,
ExerciseComment,
ExerciseImage,
Muscle
Muscle,
)
@@ -33,73 +33,88 @@ class ExerciseBaseSerializer(serializers.ModelSerializer):
"""
Exercise serializer
"""
class Meta:
model = ExerciseBase
fields = ['id',
'uuid',
'category',
'muscles',
'muscles_secondary',
'equipment',
'creation_date']
fields = [
'id',
'uuid',
'category',
'muscles',
'muscles_secondary',
'equipment',
'creation_date',
]
class EquipmentSerializer(serializers.ModelSerializer):
"""
Equipment serializer
"""
class Meta:
model = Equipment
fields = ['id',
'name']
fields = [
'id',
'name',
]
class ExerciseImageSerializer(serializers.ModelSerializer):
"""
ExerciseImage serializer
"""
class Meta:
model = ExerciseImage
fields = ['id',
'uuid',
'exercise_base',
'image',
'is_main',
'status']
fields = [
'id',
'uuid',
'exercise_base',
'image',
'is_main',
'status',
]
class ExerciseCommentSerializer(serializers.ModelSerializer):
"""
ExerciseComment serializer
"""
class Meta:
model = ExerciseComment
fields = ['id',
'exercise',
'comment']
fields = [
'id',
'exercise',
'comment',
]
class ExerciseCategorySerializer(serializers.ModelSerializer):
"""
ExerciseCategory serializer
"""
class Meta:
model = ExerciseCategory
fields = ['id',
'name']
fields = ['id', 'name']
class MuscleSerializer(serializers.ModelSerializer):
"""
Muscle serializer
"""
class Meta:
model = Muscle
fields = ['id',
'name',
'is_front',
'image_url_main',
'image_url_secondary']
fields = [
'id',
'name',
'is_front',
'image_url_main',
'image_url_secondary',
]
class ExerciseSerializer(serializers.ModelSerializer):
@@ -117,21 +132,23 @@ class ExerciseSerializer(serializers.ModelSerializer):
class Meta:
model = Exercise
fields = ("id",
"uuid",
"name",
"exercise_base",
"status",
"description",
"creation_date",
"category",
"muscles",
"muscles_secondary",
"equipment",
"language",
"license",
"license_author",
"variations")
fields = (
"id",
"uuid",
"name",
"exercise_base",
"status",
"description",
"creation_date",
"category",
"muscles",
"muscles_secondary",
"equipment",
"language",
"license",
"license_author",
"variations",
)
class ExerciseInfoSerializer(serializers.ModelSerializer):
@@ -150,18 +167,20 @@ class ExerciseInfoSerializer(serializers.ModelSerializer):
class Meta:
model = Exercise
depth = 1
fields = ["id",
"name",
"uuid",
"description",
"creation_date",
"category",
"muscles",
"muscles_secondary",
"equipment",
"language",
"license",
"license_author",
"images",
"comments",
"variations"]
fields = [
"id",
"name",
"uuid",
"description",
"creation_date",
"category",
"muscles",
"muscles_secondary",
"equipment",
"language",
"license",
"license_author",
"images",
"comments",
"variations",
]

View File

@@ -27,7 +27,7 @@ from easy_thumbnails.files import get_thumbnailer
from rest_framework import viewsets
from rest_framework.decorators import (
action,
api_view
api_view,
)
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from rest_framework.response import Response
@@ -42,7 +42,7 @@ from wger.exercises.api.serializers import (
ExerciseImageSerializer,
ExerciseInfoSerializer,
ExerciseSerializer,
MuscleSerializer
MuscleSerializer,
)
from wger.exercises.models import (
Equipment,
@@ -50,15 +50,14 @@ from wger.exercises.models import (
ExerciseCategory,
ExerciseComment,
ExerciseImage,
Muscle
Muscle,
)
from wger.utils.language import (
load_item_languages,
load_language
load_language,
)
from wger.utils.permissions import CreateOnlyPermission
logger = logging.getLogger(__name__)
@@ -71,10 +70,12 @@ class ExerciseBaseViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = ExerciseBaseSerializer
permission_classes = (IsAuthenticatedOrReadOnly, CreateOnlyPermission)
ordering_fields = '__all__'
filterset_fields = ('category',
'muscles',
'muscles_secondary',
'equipment')
filterset_fields = (
'category',
'muscles',
'muscles_secondary',
'equipment',
)
class ExerciseViewSet(viewsets.ReadOnlyModelViewSet):
@@ -86,13 +87,15 @@ class ExerciseViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = ExerciseSerializer
permission_classes = (IsAuthenticatedOrReadOnly, CreateOnlyPermission)
ordering_fields = '__all__'
filterset_fields = ('uuid',
'creation_date',
'exercise_base',
'description',
'language',
'status',
'name')
filterset_fields = (
'uuid',
'creation_date',
'exercise_base',
'description',
'language',
'status',
'name',
)
def perform_create(self, serializer):
"""
@@ -161,13 +164,14 @@ def search(request):
json_response = {}
if q:
languages = load_item_languages(LanguageConfig.SHOW_ITEM_EXERCISES,
language_code=request.GET.get('language', None))
exercises = (Exercise.objects.filter(name__icontains=q)
.filter(language__in=languages)
.filter(status=Exercise.STATUS_ACCEPTED)
.order_by('exercise_base__category__name', 'name')
.distinct())
languages = load_item_languages(
LanguageConfig.SHOW_ITEM_EXERCISES, language_code=request.GET.get('language', None)
)
exercises = (
Exercise.objects.filter(name__icontains=q).filter(language__in=languages).filter(
status=Exercise.STATUS_ACCEPTED
).order_by('exercise_base__category__name', 'name').distinct()
)
for exercise in exercises:
if exercise.main_image:
@@ -204,13 +208,15 @@ class ExerciseInfoViewset(viewsets.ReadOnlyModelViewSet):
queryset = Exercise.objects.accepted()
serializer_class = ExerciseInfoSerializer
ordering_fields = '__all__'
filterset_fields = ('creation_date',
'description',
'language',
'name',
'exercise_base',
'license',
'license_author')
filterset_fields = (
'creation_date',
'description',
'language',
'name',
'exercise_base',
'license',
'license_author',
)
class EquipmentViewSet(viewsets.ReadOnlyModelViewSet):
@@ -220,7 +226,7 @@ class EquipmentViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Equipment.objects.all()
serializer_class = EquipmentSerializer
ordering_fields = '__all__'
filterset_fields = ('name',)
filterset_fields = ('name', )
class ExerciseCategoryViewSet(viewsets.ReadOnlyModelViewSet):
@@ -230,7 +236,7 @@ class ExerciseCategoryViewSet(viewsets.ReadOnlyModelViewSet):
queryset = ExerciseCategory.objects.all()
serializer_class = ExerciseCategorySerializer
ordering_fields = '__all__'
filterset_fields = ('name',)
filterset_fields = ('name', )
class ExerciseImageViewSet(viewsets.ModelViewSet):
@@ -241,11 +247,13 @@ class ExerciseImageViewSet(viewsets.ModelViewSet):
serializer_class = ExerciseImageSerializer
permission_classes = (IsAuthenticatedOrReadOnly, CreateOnlyPermission)
ordering_fields = '__all__'
filterset_fields = ('is_main',
'status',
'exercise_base',
'license',
'license_author')
filterset_fields = (
'is_main',
'status',
'exercise_base',
'license',
'license_author',
)
@action(detail=True)
def thumbnails(self, request, pk):
@@ -283,8 +291,7 @@ class ExerciseCommentViewSet(viewsets.ReadOnlyModelViewSet):
"""
serializer_class = ExerciseCommentSerializer
ordering_fields = '__all__'
filterset_fields = ('comment',
'exercise')
filterset_fields = ('comment', 'exercise')
def get_queryset(self):
"""Filter by language for exercise comments"""
@@ -303,5 +310,4 @@ class MuscleViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Muscle.objects.all()
serializer_class = MuscleSerializer
ordering_fields = '__all__'
filterset_fields = ('name',
'is_front')
filterset_fields = ('name', 'is_front')

View File

@@ -20,20 +20,24 @@ from django import forms
# wger
from wger.exercises.models import (
ExerciseComment,
ExerciseImage
ExerciseImage,
)
class ExerciseImageForm(forms.ModelForm):
class Meta:
model = ExerciseImage
fields = ('image',
'is_main',
'license',
'license_author')
fields = (
'image',
'is_main',
'license',
'license_author',
)
class CommentForm(forms.ModelForm):
class Meta:
model = ExerciseComment
exclude = ('exercise',)
exclude = ('exercise', )

View File

@@ -21,13 +21,13 @@ import os
from django.conf import settings
from django.core.exceptions import (
ImproperlyConfigured,
ValidationError
ValidationError,
)
from django.core.files import File
from django.core.files.temp import NamedTemporaryFile
from django.core.management.base import (
BaseCommand,
CommandError
CommandError,
)
from django.core.validators import URLValidator
@@ -39,10 +39,9 @@ from requests.utils import default_user_agent
from wger import get_version
from wger.exercises.models import (
ExerciseBase,
ExerciseImage
ExerciseImage,
)
IMAGE_API = "{0}/api/v2/exerciseimage/?status=2"
@@ -54,20 +53,24 @@ class Command(BaseCommand):
be modified via the GUI.
"""
help = ('Download exercise images from wger.de and update the local database\n'
'\n'
'ATTENTION: The script will download the images from the server and add them\n'
' to your local exercises. The exercises are identified by\n'
' their UUID field, if you manually edited or changed it\n'
' the script will not be able to match them.')
help = (
'Download exercise images from wger.de and update the local database\n'
'\n'
'ATTENTION: The script will download the images from the server and add them\n'
' to your local exercises. The exercises are identified by\n'
' their UUID field, if you manually edited or changed it\n'
' the script will not be able to match them.'
)
def add_arguments(self, parser):
parser.add_argument('--remote-url',
action='store',
dest='remote_url',
default='https://wger.de',
help='Remote URL to fetch the exercises from (default: '
'https://wger.de)')
parser.add_argument(
'--remote-url',
action='store',
dest='remote_url',
default='https://wger.de',
help='Remote URL to fetch the exercises from (default: '
'https://wger.de)'
)
def handle(self, **options):

View File

@@ -37,8 +37,10 @@ class Command(BaseCommand):
exercises = Exercise.objects.all()
for exercise in exercises:
if options['verbosity'] > 1:
self.stdout.write('#{} {} -> {}'.format(exercise.id,
exercise.name,
smart_capitalize(exercise.name_original)))
self.stdout.write(
'#{} {} -> {}'.format(
exercise.id, exercise.name, smart_capitalize(exercise.name_original)
)
)
exercise.name = smart_capitalize(exercise.name_original)
exercise.save()

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# flake8: noqa
from django.db import models, migrations
import django.core.validators
import wger.exercises.models
@@ -15,114 +15,255 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Equipment',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
(
'id',
models.AutoField(
verbose_name='ID', serialize=False, auto_created=True, primary_key=True
)
),
('name', models.CharField(max_length=50, verbose_name='Name')),
],
options={
'ordering': ['name'],
},
bases=(models.Model,),
bases=(models.Model, ),
),
migrations.CreateModel(
name='Exercise',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('license_author', models.CharField(help_text='If you are not the author, enter the name or source here. This is needed for some licenses e.g. the CC-BY-SA.', max_length=50, null=True, verbose_name='Author', blank=True)),
('status', models.CharField(default=b'1', max_length=2, editable=False, choices=[(b'1', 'Pending'), (b'2', 'Accepted'), (b'3', 'Declined')])),
('description', models.TextField(max_length=2000, verbose_name='Description', validators=[django.core.validators.MinLengthValidator(40)])),
(
'id',
models.AutoField(
verbose_name='ID', serialize=False, auto_created=True, primary_key=True
)
),
(
'license_author',
models.CharField(
help_text=
'If you are not the author, enter the name or source here. This is needed for some licenses e.g. the CC-BY-SA.',
max_length=50,
null=True,
verbose_name='Author',
blank=True
)
),
(
'status',
models.CharField(
default=b'1',
max_length=2,
editable=False,
choices=[(b'1', 'Pending'), (b'2', 'Accepted'), (b'3', 'Declined')]
)
),
(
'description',
models.TextField(
max_length=2000,
verbose_name='Description',
validators=[django.core.validators.MinLengthValidator(40)]
)
),
('name', models.CharField(max_length=200, verbose_name='Name')),
('creation_date', models.DateField(auto_now_add=True, verbose_name='Date', null=True)),
(
'creation_date',
models.DateField(auto_now_add=True, verbose_name='Date', null=True)
),
],
options={
'ordering': ['name'],
},
bases=(models.Model,),
bases=(models.Model, ),
),
migrations.CreateModel(
name='ExerciseCategory',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
(
'id',
models.AutoField(
verbose_name='ID', serialize=False, auto_created=True, primary_key=True
)
),
('name', models.CharField(max_length=100, verbose_name='Name')),
],
options={
'ordering': ['name'],
'verbose_name_plural': 'Exercise Categories',
},
bases=(models.Model,),
bases=(models.Model, ),
),
migrations.CreateModel(
name='ExerciseComment',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('comment', models.CharField(help_text='A comment about how to correctly do this exercise.', max_length=200, verbose_name='Comment')),
('exercise', models.ForeignKey(editable=False, to='exercises.Exercise', verbose_name='Exercise', on_delete=models.CASCADE)),
(
'id',
models.AutoField(
verbose_name='ID', serialize=False, auto_created=True, primary_key=True
)
),
(
'comment',
models.CharField(
help_text='A comment about how to correctly do this exercise.',
max_length=200,
verbose_name='Comment'
)
),
(
'exercise',
models.ForeignKey(
editable=False,
to='exercises.Exercise',
verbose_name='Exercise',
on_delete=models.CASCADE
)
),
],
options={
},
bases=(models.Model,),
options={},
bases=(models.Model, ),
),
migrations.CreateModel(
name='ExerciseImage',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('license_author', models.CharField(help_text='If you are not the author, enter the name or source here. This is needed for some licenses e.g. the CC-BY-SA.', max_length=50, null=True, verbose_name='Author', blank=True)),
('status', models.CharField(default=b'1', max_length=2, editable=False, choices=[(b'1', 'Pending'), (b'2', 'Accepted'), (b'3', 'Declined')])),
('image', models.ImageField(help_text='Only PNG and JPEG formats are supported', upload_to=wger.exercises.models.exercise_image_upload_dir, verbose_name='Image')),
('is_main', models.BooleanField(default=False, help_text='Tick the box if you want to set this image as the main one for the exercise (will be shown e.g. in the search). The first image is automatically marked by the system.', verbose_name='Is main picture')),
('exercise', models.ForeignKey(verbose_name='Exercise', to='exercises.Exercise', on_delete=models.CASCADE)),
('license', models.ForeignKey(default=2, verbose_name='License', to='core.License', on_delete=models.CASCADE)),
(
'id',
models.AutoField(
verbose_name='ID', serialize=False, auto_created=True, primary_key=True
)
),
(
'license_author',
models.CharField(
help_text=
'If you are not the author, enter the name or source here. This is needed for some licenses e.g. the CC-BY-SA.',
max_length=50,
null=True,
verbose_name='Author',
blank=True
)
),
(
'status',
models.CharField(
default=b'1',
max_length=2,
editable=False,
choices=[(b'1', 'Pending'), (b'2', 'Accepted'), (b'3', 'Declined')]
)
),
(
'image',
models.ImageField(
help_text='Only PNG and JPEG formats are supported',
upload_to=wger.exercises.models.exercise_image_upload_dir,
verbose_name='Image'
)
),
(
'is_main',
models.BooleanField(
default=False,
help_text=
'Tick the box if you want to set this image as the main one for the exercise (will be shown e.g. in the search). The first image is automatically marked by the system.',
verbose_name='Is main picture'
)
),
(
'exercise',
models.ForeignKey(
verbose_name='Exercise', to='exercises.Exercise', on_delete=models.CASCADE
)
),
(
'license',
models.ForeignKey(
default=2,
verbose_name='License',
to='core.License',
on_delete=models.CASCADE
)
),
],
options={
'ordering': ['-is_main', 'id'],
},
bases=(models.Model,),
bases=(models.Model, ),
),
migrations.CreateModel(
name='Muscle',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(help_text='In latin, e.g. "Pectoralis major"', max_length=50, verbose_name='Name')),
(
'id',
models.AutoField(
verbose_name='ID', serialize=False, auto_created=True, primary_key=True
)
),
(
'name',
models.CharField(
help_text='In latin, e.g. "Pectoralis major"',
max_length=50,
verbose_name='Name'
)
),
('is_front', models.BooleanField(default=1)),
],
options={
'ordering': ['name'],
},
bases=(models.Model,),
bases=(models.Model, ),
),
migrations.AddField(
model_name='exercise',
name='category',
field=models.ForeignKey(verbose_name='Category', to='exercises.ExerciseCategory', on_delete=models.CASCADE),
field=models.ForeignKey(
verbose_name='Category', to='exercises.ExerciseCategory', on_delete=models.CASCADE
),
preserve_default=True,
),
migrations.AddField(
model_name='exercise',
name='equipment',
field=models.ManyToManyField(to='exercises.Equipment', null=True, verbose_name='Equipment', blank=True),
field=models.ManyToManyField(
to='exercises.Equipment', null=True, verbose_name='Equipment', blank=True
),
preserve_default=True,
),
migrations.AddField(
model_name='exercise',
name='language',
field=models.ForeignKey(verbose_name='Language', to='core.Language', on_delete=models.CASCADE),
field=models.ForeignKey(
verbose_name='Language', to='core.Language', on_delete=models.CASCADE
),
preserve_default=True,
),
migrations.AddField(
model_name='exercise',
name='license',
field=models.ForeignKey(default=2, verbose_name='License', to='core.License', on_delete=models.CASCADE),
field=models.ForeignKey(
default=2, verbose_name='License', to='core.License', on_delete=models.CASCADE
),
preserve_default=True,
),
migrations.AddField(
model_name='exercise',
name='muscles',
field=models.ManyToManyField(to='exercises.Muscle', null=True, verbose_name='Primary muscles', blank=True),
field=models.ManyToManyField(
to='exercises.Muscle', null=True, verbose_name='Primary muscles', blank=True
),
preserve_default=True,
),
migrations.AddField(
model_name='exercise',
name='muscles_secondary',
field=models.ManyToManyField(related_name='secondary_muscles', null=True, verbose_name='Secondary muscles', to='exercises.Muscle', blank=True),
field=models.ManyToManyField(
related_name='secondary_muscles',
null=True,
verbose_name='Secondary muscles',
to='exercises.Muscle',
blank=True
),
preserve_default=True,
),
]

View File

@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
# flake8: noqa
from django.db import models, migrations
import uuid
def generate_uuids(apps, schema_editor):
"""
Generate new UUIDs for each exercise
@@ -26,7 +27,9 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='exercise',
name='uuid',
field=models.CharField(editable=False, max_length=36, verbose_name='UUID', default=uuid.uuid4),
field=models.CharField(
editable=False, max_length=36, verbose_name='UUID', default=uuid.uuid4
),
preserve_default=True,
),
migrations.RunPython(generate_uuids),

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# flake8: noqa
# Generated by Django 1.9.9 on 2016-09-21 18:00
from django.db import migrations, models
@@ -21,6 +21,7 @@ def capitalize_name(apps, schema_editor):
The algorithm is copied here as it was implemented on the day the migration
was written.
"""
def capitalize(input):
out = []
for word in input.split(' '):
@@ -35,6 +36,7 @@ def capitalize_name(apps, schema_editor):
exercise.name = capitalize(exercise.name_original)
exercise.save()
class Migration(migrations.Migration):
dependencies = [

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# flake8: noqa
# Generated by Django 1.9.12 on 2017-04-04 01:14
from django.db import migrations, models
import uuid
@@ -15,22 +15,36 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='exercise',
name='equipment',
field=models.ManyToManyField(blank=True, to='exercises.Equipment', verbose_name='Equipment'),
field=models.ManyToManyField(
blank=True, to='exercises.Equipment', verbose_name='Equipment'
),
),
migrations.AlterField(
model_name='exercise',
name='muscles',
field=models.ManyToManyField(blank=True, to='exercises.Muscle', verbose_name='Primary muscles'),
field=models.ManyToManyField(
blank=True, to='exercises.Muscle', verbose_name='Primary muscles'
),
),
migrations.AlterField(
model_name='exercise',
name='muscles_secondary',
field=models.ManyToManyField(blank=True, related_name='secondary_muscles', to='exercises.Muscle', verbose_name='Secondary muscles'),
field=models.ManyToManyField(
blank=True,
related_name='secondary_muscles',
to='exercises.Muscle',
verbose_name='Secondary muscles'
),
),
migrations.AlterField(
model_name='exercise',
name='status',
field=models.CharField(choices=[('1', 'Pending'), ('2', 'Accepted'), ('3', 'Declined')], default='1', editable=False, max_length=2),
field=models.CharField(
choices=[('1', 'Pending'), ('2', 'Accepted'), ('3', 'Declined')],
default='1',
editable=False,
max_length=2
),
),
migrations.AlterField(
model_name='exercise',
@@ -40,11 +54,21 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='exerciseimage',
name='is_main',
field=models.BooleanField(default=False, help_text='Tick the box if you want to set this image as the main one for the exercise (will be shown e.g. in the search). The first image is automatically marked by the system.', verbose_name='Main picture'),
field=models.BooleanField(
default=False,
help_text=
'Tick the box if you want to set this image as the main one for the exercise (will be shown e.g. in the search). The first image is automatically marked by the system.',
verbose_name='Main picture'
),
),
migrations.AlterField(
model_name='exerciseimage',
name='status',
field=models.CharField(choices=[('1', 'Pending'), ('2', 'Accepted'), ('3', 'Declined')], default='1', editable=False, max_length=2),
field=models.CharField(
choices=[('1', 'Pending'), ('2', 'Accepted'), ('3', 'Declined')],
default='1',
editable=False,
max_length=2
),
),
]

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# flake8: noqa
# Generated by Django 1.11.21 on 2019-06-18 16:17
from django.db import migrations
@@ -13,10 +13,16 @@ class Migration(migrations.Migration):
operations = [
migrations.AlterModelOptions(
name='exercise',
options={'base_manager_name': 'objects', 'ordering': ['name']},
options={
'base_manager_name': 'objects',
'ordering': ['name']
},
),
migrations.AlterModelOptions(
name='exerciseimage',
options={'base_manager_name': 'objects', 'ordering': ['-is_main', 'id']},
options={
'base_manager_name': 'objects',
'ordering': ['-is_main', 'id']
},
),
]

View File

@@ -14,19 +14,23 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Variation',
fields=[
('id', models.AutoField(auto_created=True,
primary_key=True,
serialize=False,
verbose_name='ID')),
(
'id',
models.AutoField(
auto_created=True, primary_key=True, serialize=False, verbose_name='ID'
)
),
],
),
migrations.AddField(
model_name='exercise',
name='variations',
field=models.ForeignKey(default='',
null=True,
on_delete=django.db.models.deletion.CASCADE,
to='exercises.variation',
verbose_name='Variations'),
field=models.ForeignKey(
default='',
null=True,
on_delete=django.db.models.deletion.CASCADE,
to='exercises.variation',
verbose_name='Variations'
),
),
]

View File

@@ -2,7 +2,6 @@
from django.db import migrations
exercise_variation_ids = [
#
# exercises in English
@@ -115,13 +114,22 @@ exercise_variation_ids = [
[10, 12],
# Latzug
[158, 226, 243, 244, ],
[
158,
226,
243,
244,
],
# Rudern
[245, 59, 70, 224, 76],
# Frontdrücken
[19, 153, 66, ],
[
19,
153,
66,
],
# Shrugs
[8, 137, 67],

View File

@@ -16,36 +16,81 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='ExerciseBase',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False,
verbose_name='ID')),
('license_author', models.CharField(blank=True,
help_text='If you are not the author, enter \
(
'id',
models.AutoField(
auto_created=True, primary_key=True, serialize=False, verbose_name='ID'
)
),
(
'license_author',
models.CharField(
blank=True,
help_text='If you are not the author, enter \
the name or source here. This is \
needed for some licenses e.g. the \
CC-BY-SA.',
max_length=50, null=True,
verbose_name='Author')),
('status', models.CharField(choices=[('1', 'Pending'), ('2', 'Accepted'),
('3', 'Declined')],
default='1', editable=False, max_length=2)),
('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
to='exercises.exercisecategory',
verbose_name='Category')),
('equipment', models.ManyToManyField(blank=True, to='exercises.Equipment',
verbose_name='Equipment')),
('license', models.ForeignKey(default=2,
on_delete=django.db.models.deletion.CASCADE,
to='core.license', verbose_name='License')),
('muscles', models.ManyToManyField(blank=True, to='exercises.Muscle',
verbose_name='Primary muscles')),
('muscles_secondary', models.ManyToManyField(blank=True,
related_name='secondary_muscles_base',
to='exercises.Muscle',
verbose_name='Secondary muscles')),
('variations', models.ForeignKey(null=True,
on_delete=django.db.models.deletion.CASCADE,
to='exercises.variation',
verbose_name='Variations')),
max_length=50,
null=True,
verbose_name='Author'
)
),
(
'status',
models.CharField(
choices=[('1', 'Pending'), ('2', 'Accepted'), ('3', 'Declined')],
default='1',
editable=False,
max_length=2
)
),
(
'category',
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to='exercises.exercisecategory',
verbose_name='Category'
)
),
(
'equipment',
models.ManyToManyField(
blank=True, to='exercises.Equipment', verbose_name='Equipment'
)
),
(
'license',
models.ForeignKey(
default=2,
on_delete=django.db.models.deletion.CASCADE,
to='core.license',
verbose_name='License'
)
),
(
'muscles',
models.ManyToManyField(
blank=True, to='exercises.Muscle', verbose_name='Primary muscles'
)
),
(
'muscles_secondary',
models.ManyToManyField(
blank=True,
related_name='secondary_muscles_base',
to='exercises.Muscle',
verbose_name='Secondary muscles'
)
),
(
'variations',
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
to='exercises.variation',
verbose_name='Variations'
)
),
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, verbose_name='UUID')),
],
options={
@@ -55,15 +100,22 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='exercise',
name='exercise_base',
field=models.ForeignKey(default=None, null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='exercises', to='exercises.exercisebase',
verbose_name='ExerciseBase'),
field=models.ForeignKey(
default=None,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='exercises',
to='exercises.exercisebase',
verbose_name='ExerciseBase'
),
),
migrations.AlterField(
model_name='exerciseimage',
name='exercise',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
to='exercises.exercisebase', verbose_name='Exercise'),
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to='exercises.exercisebase',
verbose_name='Exercise'
),
),
]

View File

@@ -8,9 +8,7 @@ def copy_columns(apps, schema_editor):
ExerciseBase = apps.get_model('exercises', 'ExerciseBase')
for exercise in Exercise.objects.all():
exercise_base = ExerciseBase.objects.create(
category=exercise.category,
)
exercise_base = ExerciseBase.objects.create(category=exercise.category, )
exercise_base.equipment.set(exercise.equipment.all())
exercise_base.muscles.set(exercise.muscles.all())
exercise_base.muscles_secondary.set(exercise.muscles_secondary.all())

View File

@@ -6,368 +6,636 @@ exercise_mapping = [
# 111 c4856da3-8454-4857-8997-336d06df590f Squats
# 7 36c27886-b10b-41b6-ac60-f02ee3784041 Kniebeuge
# 608 993cf4e0-e664-4cc2-aa46-683c6fc7a74f Приседания
{'en': 'c4856da3-8454-4857-8997-336d06df590f',
'de': '36c27886-b10b-41b6-ac60-f02ee3784041',
'ru': '993cf4e0-e664-4cc2-aa46-683c6fc7a74f'},
{
'en': 'c4856da3-8454-4857-8997-336d06df590f',
'de': '36c27886-b10b-41b6-ac60-f02ee3784041',
'ru': '993cf4e0-e664-4cc2-aa46-683c6fc7a74f'
},
# 160 b1d6d536-7f4a-4dd3-8b76-62d7a984e115 Pistol Squat
# 358 40a0019e-eaf2-491f-a134-9fdd51121358 Einbeinige Kniebeuge (Pistol Squat)
{'en': 'b1d6d536-7f4a-4dd3-8b76-62d7a984e115', 'de': '40a0019e-eaf2-491f-a134-9fdd51121358'},
{
'en': 'b1d6d536-7f4a-4dd3-8b76-62d7a984e115',
'de': '40a0019e-eaf2-491f-a134-9fdd51121358'
},
# 346 1d90f3a8-56e4-4c15-a4b4-94fc0e114e8c Bulgarian Split Squat
# 695 cf21383b-b96a-40b5-b047-b9c3ddcab963 Bulgarian Split Squat
{'en': '1d90f3a8-56e4-4c15-a4b4-94fc0e114e8c', 'de': 'cf21383b-b96a-40b5-b047-b9c3ddcab963'},
{
'en': '1d90f3a8-56e4-4c15-a4b4-94fc0e114e8c',
'de': 'cf21383b-b96a-40b5-b047-b9c3ddcab963'
},
# 202 eed05679-d1cb-44a2-a17d-dd5b0097c874 Pendelay Rows
# 731 196f9a72-7518-45c4-a4f2-d3ba3e0d3b3e Pendelay Rows
{'en': 'eed05679-d1cb-44a2-a17d-dd5b0097c874', 'de': '196f9a72-7518-45c4-a4f2-d3ba3e0d3b3e'},
{
'en': 'eed05679-d1cb-44a2-a17d-dd5b0097c874',
'de': '196f9a72-7518-45c4-a4f2-d3ba3e0d3b3e'
},
# 130 2966e4a2-4306-4cb5-a2dc-8951cd192555 Leg Press on Hackenschmidt Machine
# 402 634dcf89-c346-4af8-8b09-61f238798877 Kniebeuge an Hackenschmidtmaschine
{'en': '2966e4a2-4306-4cb5-a2dc-8951cd192555', 'de': '634dcf89-c346-4af8-8b09-61f238798877'},
{
'en': '2966e4a2-4306-4cb5-a2dc-8951cd192555',
'de': '634dcf89-c346-4af8-8b09-61f238798877'
},
# 104 e7677699-5e4f-49e6-a645-e8915a203c4d Calf Raises on Hackenschmitt Machine
# 23 c7b98fb5-7723-471a-92a1-7b4e892a5468 Wadenheben an Hackenschmidt
{'en': 'e7677699-5e4f-49e6-a645-e8915a203c4d', 'de': 'c7b98fb5-7723-471a-92a1-7b4e892a5468'},
{
'en': 'e7677699-5e4f-49e6-a645-e8915a203c4d',
'de': 'c7b98fb5-7723-471a-92a1-7b4e892a5468'
},
# 105 22cca8fc-cfaf-4941-b0f7-faf9f2937c52 Deadlifts
# 9 521a5e4f-6f35-43e5-9d1c-6e75c4956e96 Kreuzheben
{'en': '22cca8fc-cfaf-4941-b0f7-faf9f2937c52', 'de': '521a5e4f-6f35-43e5-9d1c-6e75c4956e96'},
{
'en': '22cca8fc-cfaf-4941-b0f7-faf9f2937c52',
'de': '521a5e4f-6f35-43e5-9d1c-6e75c4956e96'
},
# 570 f4dd363c-b49c-419a-b8ae-0d8e18728d8e Sumo Squats
# 762 b399006c-bb45-451e-908e-1b279b2a42a6 Sumo Squats (Pilé Squats) Kniebeuge
{'en': 'f4dd363c-b49c-419a-b8ae-0d8e18728d8e', 'de': 'b399006c-bb45-451e-908e-1b279b2a42a6'},
{
'en': 'f4dd363c-b49c-419a-b8ae-0d8e18728d8e',
'de': 'b399006c-bb45-451e-908e-1b279b2a42a6'
},
# 91 d325dd5c-6833-41c7-8eea-6b95c4871133 Crunches
# 4 0e10ac9b-ed1d-42c9-b8cc-123c22ccc5d5 Crunches
{'en': 'd325dd5c-6833-41c7-8eea-6b95c4871133', 'de': '0e10ac9b-ed1d-42c9-b8cc-123c22ccc5d5'},
{
'en': 'd325dd5c-6833-41c7-8eea-6b95c4871133',
'de': '0e10ac9b-ed1d-42c9-b8cc-123c22ccc5d5'
},
# 92 8d6c13c6-256d-4137-b1c6-b0e817697639 Crunches With Cable
# 33 5c47b690-d19f-4523-9c47-47160693eefc Crunches am Seil
{'en': '8d6c13c6-256d-4137-b1c6-b0e817697639', 'de': '5c47b690-d19f-4523-9c47-47160693eefc'},
{
'en': '8d6c13c6-256d-4137-b1c6-b0e817697639',
'de': '5c47b690-d19f-4523-9c47-47160693eefc'
},
# 94 6709577b-95ec-4053-a822-d5fe1f753966 Crunches on Machine
# 51 efbece79-4384-49d1-bb88-e16fcd8de5aa Crunches an Maschine
{'en': '6709577b-95ec-4053-a822-d5fe1f753966', 'de': 'efbece79-4384-49d1-bb88-e16fcd8de5aa'},
{
'en': '6709577b-95ec-4053-a822-d5fe1f753966',
'de': 'efbece79-4384-49d1-bb88-e16fcd8de5aa'
},
# 307 1b8b1657-40fd-4e3b-97b7-1c79b1079f8e Bear Walk
# 718 e4b3da5b-1803-42af-a50e-ffbac3853cd0 Bear Walk
{'en': '1b8b1657-40fd-4e3b-97b7-1c79b1079f8e', 'de': 'e4b3da5b-1803-42af-a50e-ffbac3853cd0'},
{
'en': '1b8b1657-40fd-4e3b-97b7-1c79b1079f8e',
'de': 'e4b3da5b-1803-42af-a50e-ffbac3853cd0'
},
# 192 5da6340b-22ec-4c1b-a443-eef2f59f92f0 Bench Press
# 15 198dcb2e-e35f-4b69-ae8b-e1124d438eae Bankdrücken LH
{'en': '5da6340b-22ec-4c1b-a443-eef2f59f92f0', 'de': '198dcb2e-e35f-4b69-ae8b-e1124d438eae'},
{
'en': '5da6340b-22ec-4c1b-a443-eef2f59f92f0',
'de': '198dcb2e-e35f-4b69-ae8b-e1124d438eae'
},
# 88 03d821e7-e1ac-4026-903b-d406381cbf76 Bench Press Narrow Grip
# 38 4def60e7-ed8d-4a9d-bf76-ceb15ecf9779 Bankdrücken Eng
{'en': '03d821e7-e1ac-4026-903b-d406381cbf76', 'de': '4def60e7-ed8d-4a9d-bf76-ceb15ecf9779'},
{
'en': '03d821e7-e1ac-4026-903b-d406381cbf76',
'de': '4def60e7-ed8d-4a9d-bf76-ceb15ecf9779'
},
# 97 0ec76f5d-1311-4d6d-bf79-00fa17c3061a Benchpress Dumbbells
# 77 06450bcb-03a8-4bd7-8349-ef677ee57ea3 Bankdrücken KH
{'en': '0ec76f5d-1311-4d6d-bf79-00fa17c3061a', 'de': '06450bcb-03a8-4bd7-8349-ef677ee57ea3'},
{
'en': '0ec76f5d-1311-4d6d-bf79-00fa17c3061a',
'de': '06450bcb-03a8-4bd7-8349-ef677ee57ea3'
},
# 129 8c6c1544-cbf8-403c-ae12-b27b392702f8 Biceps Curl With Cable
# 3 ef487de3-f071-41bf-85a6-6e76afe9c732 Bizeps am Kabel
{'en': '8c6c1544-cbf8-403c-ae12-b27b392702f8', 'de': 'ef487de3-f071-41bf-85a6-6e76afe9c732'},
{
'en': '8c6c1544-cbf8-403c-ae12-b27b392702f8',
'de': 'ef487de3-f071-41bf-85a6-6e76afe9c732'
},
# 80 38919515-ce04-4383-9c3f-5846edd0e844 Biceps Curls With SZ-bar
# 44 8277124a-9ef2-442a-9824-7ab4439a5f1f Bizeps Curls Mit ß-Stange
{'en': '38919515-ce04-4383-9c3f-5846edd0e844', 'de': '8277124a-9ef2-442a-9824-7ab4439a5f1f'},
{
'en': '38919515-ce04-4383-9c3f-5846edd0e844',
'de': '8277124a-9ef2-442a-9824-7ab4439a5f1f'
},
# 74 c56078d2-ae85-4524-a467-d1e143b6df1a Biceps Curls With Barbell
# 24 2e3fa138-3894-4f61-959a-d8966804a1a3 Bizeps LH-Curls
{'en': 'c56078d2-ae85-4524-a467-d1e143b6df1a', 'de': '2e3fa138-3894-4f61-959a-d8966804a1a3'},
{
'en': 'c56078d2-ae85-4524-a467-d1e143b6df1a',
'de': '2e3fa138-3894-4f61-959a-d8966804a1a3'
},
# 275 dfa090e4-77ae-40ed-86c0-4696fe93dcf1 Dumbbell Concentration Curl
# 681 ec0c53a5-15b9-4c2f-ac7c-00888db9f8ee Konzentrations-Curls
{'en': 'dfa090e4-77ae-40ed-86c0-4696fe93dcf1', 'de': 'ec0c53a5-15b9-4c2f-ac7c-00888db9f8ee'},
{
'en': 'dfa090e4-77ae-40ed-86c0-4696fe93dcf1',
'de': 'ec0c53a5-15b9-4c2f-ac7c-00888db9f8ee'
},
# 86 6dcc9adb-939c-4581-9e44-d0d73753997b Hammercurls
# 46 ff454f5a-70ee-40fb-9200-5e7e42960ef0 Hammercurls
{'en': '6dcc9adb-939c-4581-9e44-d0d73753997b', 'de': 'ff454f5a-70ee-40fb-9200-5e7e42960ef0'},
{
'en': '6dcc9adb-939c-4581-9e44-d0d73753997b',
'de': 'ff454f5a-70ee-40fb-9200-5e7e42960ef0'
},
# 138 5baf40e5-ea3c-4f8d-b60a-d294ee2de55b Hammercurls on Cable
# 134 06e9de49-ac1b-45c1-b289-475118932434 Hammercurls am Seil
{'en': '5baf40e5-ea3c-4f8d-b60a-d294ee2de55b', 'de': '06e9de49-ac1b-45c1-b289-475118932434'},
{
'en': '5baf40e5-ea3c-4f8d-b60a-d294ee2de55b',
'de': '06e9de49-ac1b-45c1-b289-475118932434'
},
# 771 bdcae845-6726-457f-8e04-203754a78e1c Reverse Curl
# 815 7662a76a-3ea7-4b63-86d9-c0702b554090 Reverse Curls
{'en': 'bdcae845-6726-457f-8e04-203754a78e1c', 'de': '7662a76a-3ea7-4b63-86d9-c0702b554090'},
{
'en': 'bdcae845-6726-457f-8e04-203754a78e1c',
'de': '7662a76a-3ea7-4b63-86d9-c0702b554090'
},
# 298 fa56d30a-7a8f-4084-aa68-46bd52f97959 Dumbbell Incline Curl
# 242 0842e81e-7b90-4d68-8e6b-e9a7c0186b54 Bizeps KH-Curls Schrägbank
{'en': 'fa56d30a-7a8f-4084-aa68-46bd52f97959', 'de': '0842e81e-7b90-4d68-8e6b-e9a7c0186b54'},
{
'en': 'fa56d30a-7a8f-4084-aa68-46bd52f97959',
'de': '0842e81e-7b90-4d68-8e6b-e9a7c0186b54'
},
# 81 48a59aa8-4568-409c-8afe-f8cb99c558ea Biceps Curls With Dumbbell
# 26 8cbbffcc-1989-43de-9200-03869480398c Bizeps KH-Curls
{'en': '48a59aa8-4568-409c-8afe-f8cb99c558ea', 'de': '8cbbffcc-1989-43de-9200-03869480398c'},
{
'en': '48a59aa8-4568-409c-8afe-f8cb99c558ea',
'de': '8cbbffcc-1989-43de-9200-03869480398c'
},
# 227 53ca25b3-61d9-4f72-bfdb-492b83484ff5 Arnold Shoulder Press
# 228 880bff63-6798-4ffc-a818-b2a1ccfec0f7 Arnold Press
{'en': '53ca25b3-61d9-4f72-bfdb-492b83484ff5', 'de': '880bff63-6798-4ffc-a818-b2a1ccfec0f7'},
{
'en': '53ca25b3-61d9-4f72-bfdb-492b83484ff5',
'de': '880bff63-6798-4ffc-a818-b2a1ccfec0f7'
},
# 150 3721be0c-2a59-4bb9-90d3-695faaf028af Shrugs, Barbells
# 137 a63d40df-a872-44e7-87d2-9b58b67d5406 Shrugs (Schulterheben) Mit LH
{'en': '3721be0c-2a59-4bb9-90d3-695faaf028af', 'de': 'a63d40df-a872-44e7-87d2-9b58b67d5406'},
{
'en': '3721be0c-2a59-4bb9-90d3-695faaf028af',
'de': 'a63d40df-a872-44e7-87d2-9b58b67d5406'
},
# 151 ee61aef0-f0c7-4a7a-882a-3b39312dfffd Shrugs, Dumbbells
# 8 6dde45fc-7fdf-4523-a039-9c373677a750 Shrugs (Schulterheben) Mit KH
{'en': 'ee61aef0-f0c7-4a7a-882a-3b39312dfffd', 'de': '6dde45fc-7fdf-4523-a039-9c373677a750'},
{
'en': 'ee61aef0-f0c7-4a7a-882a-3b39312dfffd',
'de': '6dde45fc-7fdf-4523-a039-9c373677a750'
},
# 387 b229c57f-5363-41e2-add7-c2501a31de0b Wall Squat
# 707 7f3e45fa-3b17-4cdb-90d5-cb9957212cbf Wall Squat
{'en': 'b229c57f-5363-41e2-add7-c2501a31de0b', 'de': '7f3e45fa-3b17-4cdb-90d5-cb9957212cbf'},
{
'en': 'b229c57f-5363-41e2-add7-c2501a31de0b',
'de': '7f3e45fa-3b17-4cdb-90d5-cb9957212cbf'
},
# 93 4bd55b0a-559a-4458-aabd-e66619b63610 Negative Crunches
# 32 f5e98b51-6c0e-4c77-94ec-158210669f6d Crunches an Negativbank
{'en': '4bd55b0a-559a-4458-aabd-e66619b63610', 'de': 'f5e98b51-6c0e-4c77-94ec-158210669f6d'},
{
'en': '4bd55b0a-559a-4458-aabd-e66619b63610',
'de': 'f5e98b51-6c0e-4c77-94ec-158210669f6d'
},
# 330 6572a8e9-083c-4622-8f0c-6107a013a490 Superman
# 735 bd58585e-4bf4-46cc-ba38-717a7857e21c Superman
{'en': '6572a8e9-083c-4622-8f0c-6107a013a490', 'de': 'bd58585e-4bf4-46cc-ba38-717a7857e21c'},
{
'en': '6572a8e9-083c-4622-8f0c-6107a013a490',
'de': 'bd58585e-4bf4-46cc-ba38-717a7857e21c'
},
# 238 7729ffe5-a896-4deb-80e0-f4e6163c0c09 Plank
# 417 257cdb33-ba8b-410a-9396-5132b08dc912 Unterarmstütze - Plank
{'en': '7729ffe5-a896-4deb-80e0-f4e6163c0c09', 'de': '257cdb33-ba8b-410a-9396-5132b08dc912'},
{
'en': '7729ffe5-a896-4deb-80e0-f4e6163c0c09',
'de': '257cdb33-ba8b-410a-9396-5132b08dc912'
},
# 325 a36f852f-a29f-4f93-81b6-1012047ac1ee Side Plank
# 715 2c6cd166-40e1-4b68-96b7-c30b31e28b99 Side Plank
{'en': 'a36f852f-a29f-4f93-81b6-1012047ac1ee', 'de': '2c6cd166-40e1-4b68-96b7-c30b31e28b99'},
{
'en': 'a36f852f-a29f-4f93-81b6-1012047ac1ee',
'de': '2c6cd166-40e1-4b68-96b7-c30b31e28b99'
},
# 116 6e862700-3d63-486c-99ae-744c68d2f753 Good Mornings
# 50 a311a463-f219-4741-b4fb-247013dc8dd8 Good Mornings
{'en': '6e862700-3d63-486c-99ae-744c68d2f753', 'de': 'a311a463-f219-4741-b4fb-247013dc8dd8'},
{
'en': '6e862700-3d63-486c-99ae-744c68d2f753',
'de': 'a311a463-f219-4741-b4fb-247013dc8dd8'
},
# 326 bf9e572d-d138-43e9-a486-a5c6ad9033f8 Full Sit Outs
# 710 143cf744-ce94-458c-9c74-1aea6c64cfc7 Full Sit Outs
{'en': 'bf9e572d-d138-43e9-a486-a5c6ad9033f8', 'de': '143cf744-ce94-458c-9c74-1aea6c64cfc7'},
{
'en': 'bf9e572d-d138-43e9-a486-a5c6ad9033f8',
'de': '143cf744-ce94-458c-9c74-1aea6c64cfc7'
},
# 95 fb750082-7034-4c51-b1e4-dfa0fe2dac8e Sit-ups
# 57 60f329dd-f8ab-469a-8a88-39f80275b3a7 Sit Ups
{'en': 'fb750082-7034-4c51-b1e4-dfa0fe2dac8e', 'de': '60f329dd-f8ab-469a-8a88-39f80275b3a7'},
{
'en': 'fb750082-7034-4c51-b1e4-dfa0fe2dac8e',
'de': '60f329dd-f8ab-469a-8a88-39f80275b3a7'
},
# 83 aa4fcd9b-baee-41cf-b4c5-8462bc43a8be Dips Between Two Benches
# 68 011b956d-33b3-4600-9e42-2e7dcbac9fb5 Dips Zwischen 2 Bänke
{'en': 'aa4fcd9b-baee-41cf-b4c5-8462bc43a8be', 'de': '011b956d-33b3-4600-9e42-2e7dcbac9fb5'},
{
'en': 'aa4fcd9b-baee-41cf-b4c5-8462bc43a8be',
'de': '011b956d-33b3-4600-9e42-2e7dcbac9fb5'
},
# 354 6335de72-146f-4f38-886d-6b8a27db62ff Burpees
# 719 5bae16b0-cefb-4fc7-be2b-e31794335c42 Burpees
{'en': '6335de72-146f-4f38-886d-6b8a27db62ff', 'de': '5bae16b0-cefb-4fc7-be2b-e31794335c42'},
{
'en': '6335de72-146f-4f38-886d-6b8a27db62ff',
'de': '5bae16b0-cefb-4fc7-be2b-e31794335c42'
},
# 289 6add5973-86d0-4543-928a-6bb8b3f34efc Axe Hold
# 677 8e9d8968-323d-468c-9174-8cf11a105fad Axe Hold
{'en': '6add5973-86d0-4543-928a-6bb8b3f34efc', 'de': '8e9d8968-323d-468c-9174-8cf11a105fad'},
{
'en': '6add5973-86d0-4543-928a-6bb8b3f34efc',
'de': '8e9d8968-323d-468c-9174-8cf11a105fad'
},
# 98 3c3857f8-d224-4d5a-8cc1-f4e7982d3475 Butterfly
# 30 9df24c6f-016b-4623-8878-f71c235c50fa Butterfly
{'en': '3c3857f8-d224-4d5a-8cc1-f4e7982d3475', 'de': '9df24c6f-016b-4623-8878-f71c235c50fa'},
{
'en': '3c3857f8-d224-4d5a-8cc1-f4e7982d3475',
'de': '9df24c6f-016b-4623-8878-f71c235c50fa'
},
# 99 08637e04-d995-4c07-b021-a20f26b6fd97 Butterfly Narrow Grip
# 52 44b0d79a-5c5b-43df-bf62-3e67148f1c33 Butterfly Eng
{'en': '08637e04-d995-4c07-b021-a20f26b6fd97', 'de': '44b0d79a-5c5b-43df-bf62-3e67148f1c33'},
{
'en': '08637e04-d995-4c07-b021-a20f26b6fd97',
'de': '44b0d79a-5c5b-43df-bf62-3e67148f1c33'
},
# 124 8715a96e-c8a2-458a-b023-4ea7d82fdab8 Butterfly Reverse
# 21 605b4a25-bc1d-4604-bd93-c85b2aaf84ae Butterfly Reverse
{'en': '8715a96e-c8a2-458a-b023-4ea7d82fdab8', 'de': '605b4a25-bc1d-4604-bd93-c85b2aaf84ae'},
{
'en': '8715a96e-c8a2-458a-b023-4ea7d82fdab8',
'de': '605b4a25-bc1d-4604-bd93-c85b2aaf84ae'
},
# 394 659f3fb9-2370-42fb-9c29-9aaf65c7a7de Facepull
# 415 237c8770-4c1f-433d-b8b4-b1ae5c69bbc5 Face Pulls
{'en': '659f3fb9-2370-42fb-9c29-9aaf65c7a7de', 'de': '237c8770-4c1f-433d-b8b4-b1ae5c69bbc5'},
{
'en': '659f3fb9-2370-42fb-9c29-9aaf65c7a7de',
'de': '237c8770-4c1f-433d-b8b4-b1ae5c69bbc5'
},
# 281 e1881607-9418-4bcc-8c69-2301a579637e L Hold
# 753 6ae461d7-daea-4ac7-999b-826fe28263b0 L Hold
{'en': 'e1881607-9418-4bcc-8c69-2301a579637e', 'de': '6ae461d7-daea-4ac7-999b-826fe28263b0'},
{
'en': 'e1881607-9418-4bcc-8c69-2301a579637e',
'de': '6ae461d7-daea-4ac7-999b-826fe28263b0'
},
# 143 b192c4eb-31c6-458e-b566-d3f38b5aa30c Long-Pulley (low Row)
# 37 91fa0487-b137-4bef-86cf-39816cd5ee48 Long-Pulley
{'en': 'b192c4eb-31c6-458e-b566-d3f38b5aa30c', 'de': '91fa0487-b137-4bef-86cf-39816cd5ee48'},
{
'en': 'b192c4eb-31c6-458e-b566-d3f38b5aa30c',
'de': '91fa0487-b137-4bef-86cf-39816cd5ee48'
},
# 144 bcabc8cf-c005-4630-8853-ef4922e7fd91 Long-Pulley, Narrow
# 136 490a615c-4185-4560-88a5-020f3aa40be2 Long-Pulley (eng)
{'en': 'bcabc8cf-c005-4630-8853-ef4922e7fd91', 'de': '490a615c-4185-4560-88a5-020f3aa40be2'},
{
'en': 'bcabc8cf-c005-4630-8853-ef4922e7fd91',
'de': '490a615c-4185-4560-88a5-020f3aa40be2'
},
# 103 399666e7-30a7-4b86-833d-4422e4c72b61 Sitting Calf Raises
# 14 47b4d57a-3bb3-4a03-a522-a0559a254730 Wadenheben Sitzend
{'en': '399666e7-30a7-4b86-833d-4422e4c72b61', 'de': '47b4d57a-3bb3-4a03-a522-a0559a254730'},
{
'en': '399666e7-30a7-4b86-833d-4422e4c72b61',
'de': '47b4d57a-3bb3-4a03-a522-a0559a254730'
},
# 102 abc4c62e-27b2-4c31-8766-40f8dca84cab Standing Calf Raises
# 13 283d4ec5-1b29-41bb-997d-74c30e7a68b5 Wadenheben Stehend
{'en': 'abc4c62e-27b2-4c31-8766-40f8dca84cab', 'de': '283d4ec5-1b29-41bb-997d-74c30e7a68b5'},
{
'en': 'abc4c62e-27b2-4c31-8766-40f8dca84cab',
'de': '283d4ec5-1b29-41bb-997d-74c30e7a68b5'
},
# 308 074530d6-801a-404b-b3b7-adc207be69be Calf Press Using Leg Press Machine
# 745 84afd45e-235a-41c3-8711-ea9f6d08eeb6 Wadendrücken an Beinpresse
{'en': '074530d6-801a-404b-b3b7-adc207be69be', 'de': '84afd45e-235a-41c3-8711-ea9f6d08eeb6'},
{
'en': '074530d6-801a-404b-b3b7-adc207be69be',
'de': '84afd45e-235a-41c3-8711-ea9f6d08eeb6'
},
# 85 ee00d53e-4482-44aa-b780-bbc570061841 French Press (skullcrusher) Dumbbells
# 58 bdf8997a-84ce-434a-ae95-1f9fc50f43bb Frenchpress KH
{'en': 'ee00d53e-4482-44aa-b780-bbc570061841', 'de': 'bdf8997a-84ce-434a-ae95-1f9fc50f43bb'},
{
'en': 'ee00d53e-4482-44aa-b780-bbc570061841',
'de': 'bdf8997a-84ce-434a-ae95-1f9fc50f43bb'
},
# 84 ee4a350b-c681-407f-a414-6ec243809ec7 French Press (skullcrusher) SZ-bar
# 25 9972e42d-43d8-43c0-b547-4638ca3e47be Frenchpress ß-Stange
{'en': 'ee4a350b-c681-407f-a414-6ec243809ec7', 'de': '9972e42d-43d8-43c0-b547-4638ca3e47be'},
{
'en': 'ee4a350b-c681-407f-a414-6ec243809ec7',
'de': '9972e42d-43d8-43c0-b547-4638ca3e47be'
},
# 788 6c8e863c-f6c2-4ad0-a0e9-5e6d9e6a928b Leg Press
# 6 a27cfdd9-5299-49ab-85f5-6e4042657549 Beinpresse
{'en': '6c8e863c-f6c2-4ad0-a0e9-5e6d9e6a928b', 'de': 'a27cfdd9-5299-49ab-85f5-6e4042657549'},
{
'en': '6c8e863c-f6c2-4ad0-a0e9-5e6d9e6a928b',
'de': 'a27cfdd9-5299-49ab-85f5-6e4042657549'
},
# 115 560e1a20-33e1-45db-ba5b-63f8c51af76d Leg Presses (narrow)
# 54 e7d0cc2d-e28b-479f-91d9-7eab7b686fd8 Beinpresse Eng
{'en': '560e1a20-33e1-45db-ba5b-63f8c51af76d', 'de': 'e7d0cc2d-e28b-479f-91d9-7eab7b686fd8'},
{
'en': '560e1a20-33e1-45db-ba5b-63f8c51af76d',
'de': 'e7d0cc2d-e28b-479f-91d9-7eab7b686fd8'
},
# 112 587c0052-f2bc-48ca-8af9-415175308901 Dumbbell Lunges Standing
# 55 27301836-ed7f-4510-83e7-66c0b8041a44 Ausfallschritte Stehend
# 363 c38e34a7-5560-46a1-bae4-c4fce9619e55 Výpad Statický
{'en': '587c0052-f2bc-48ca-8af9-415175308901',
'de': '27301836-ed7f-4510-83e7-66c0b8041a44',
'cs': 'c38e34a7-5560-46a1-bae4-c4fce9619e55'},
{
'en': '587c0052-f2bc-48ca-8af9-415175308901',
'de': '27301836-ed7f-4510-83e7-66c0b8041a44',
'cs': 'c38e34a7-5560-46a1-bae4-c4fce9619e55'
},
# 113 ffd4ce7e-e14f-49d4-9dc9-dc1362631382 Dumbbell Lunges Walking
# 5 5675ae61-6597-4806-ae5c-2dda5a5ac03c Ausfallschritte im Gehen
{'en': 'ffd4ce7e-e14f-49d4-9dc9-dc1362631382', 'de': '5675ae61-6597-4806-ae5c-2dda5a5ac03c'},
{
'en': 'ffd4ce7e-e14f-49d4-9dc9-dc1362631382',
'de': '5675ae61-6597-4806-ae5c-2dda5a5ac03c'
},
# 145 754391c6-39d5-4bb6-a311-68a520f6fd3a Fly With Dumbbells
# 18 c0119734-a412-4f34-91f1-cd1c1177fcb8 Fliegende KH Flachbank
{'en': '754391c6-39d5-4bb6-a311-68a520f6fd3a', 'de': 'c0119734-a412-4f34-91f1-cd1c1177fcb8'},
{
'en': '754391c6-39d5-4bb6-a311-68a520f6fd3a',
'de': 'c0119734-a412-4f34-91f1-cd1c1177fcb8'
},
# 146 acf1f2df-46c4-49a3-8e6c-2979ea4204b1 Fly With Dumbbells, Decline Bench
# 73 3b34d81d-7882-46a6-bb83-40f84d3f8300 Fliegende KH Schrägbank
{'en': 'acf1f2df-46c4-49a3-8e6c-2979ea4204b1', 'de': '3b34d81d-7882-46a6-bb83-40f84d3f8300'},
{
'en': 'acf1f2df-46c4-49a3-8e6c-2979ea4204b1',
'de': '3b34d81d-7882-46a6-bb83-40f84d3f8300'
},
# 409 75ec610d-216a-49fe-b395-c5fd8ee14b53 Reverse Plank
# 713 24e5637e-60eb-41f5-b964-31e2af990bfc Reverse Plank
{'en': '75ec610d-216a-49fe-b395-c5fd8ee14b53', 'de': '24e5637e-60eb-41f5-b964-31e2af990bfc'},
{
'en': '75ec610d-216a-49fe-b395-c5fd8ee14b53',
'de': '24e5637e-60eb-41f5-b964-31e2af990bfc'
},
# 181 f6c4e2fa-226d-46e8-87dc-75fc8cd628bd Chin-ups
# 747 9e71d2a3-9021-4270-a7b9-0d8bd28e7394 Chin-ups
{'en': 'f6c4e2fa-226d-46e8-87dc-75fc8cd628bd', 'de': '9e71d2a3-9021-4270-a7b9-0d8bd28e7394'},
{
'en': 'f6c4e2fa-226d-46e8-87dc-75fc8cd628bd',
'de': '9e71d2a3-9021-4270-a7b9-0d8bd28e7394'
},
# 346 1d90f3a8-56e4-4c15-a4b4-94fc0e114e8c Bulgarian Split Squat
# 695 cf21383b-b96a-40b5-b047-b9c3ddcab963 Bulgarian Split Squat
{'en': '1d90f3a8-56e4-4c15-a4b4-94fc0e114e8c', 'de': 'cf21383b-b96a-40b5-b047-b9c3ddcab963'},
{
'en': '1d90f3a8-56e4-4c15-a4b4-94fc0e114e8c',
'de': 'cf21383b-b96a-40b5-b047-b9c3ddcab963'
},
# 781 7191e015-0c60-4376-9be0-733b9754b7f8 Dips
# 29 41b495fd-562e-4d6e-942a-60dc52d5e194 Dips
{'en': '7191e015-0c60-4376-9be0-733b9754b7f8', 'de': '41b495fd-562e-4d6e-942a-60dc52d5e194'},
{
'en': '7191e015-0c60-4376-9be0-733b9754b7f8',
'de': '41b495fd-562e-4d6e-942a-60dc52d5e194'
},
# 279 88568115-5e88-4afc-b005-e2f7d453ac13 Tricep Dumbbell Kickback
# 232 ebe60386-bf33-4f50-87a6-d44c5f454158 Kick-Backs
{'en': '88568115-5e88-4afc-b005-e2f7d453ac13', 'de': 'ebe60386-bf33-4f50-87a6-d44c5f454158'},
{
'en': '88568115-5e88-4afc-b005-e2f7d453ac13',
'de': 'ebe60386-bf33-4f50-87a6-d44c5f454158'
},
# 128 f57a0c60-7d37-4eb3-a94e-1a90292e8c02 Hyperextensions
# 60 913d71cc-ba7a-4902-bfa8-3c5203129d72 Hyperextensions
{'en': 'f57a0c60-7d37-4eb3-a94e-1a90292e8c02', 'de': '913d71cc-ba7a-4902-bfa8-3c5203129d72'},
{
'en': 'f57a0c60-7d37-4eb3-a94e-1a90292e8c02',
'de': '913d71cc-ba7a-4902-bfa8-3c5203129d72'
},
# 195 9d756e84-6b77-4f17-b21d-7d266d6a8bd2 Push Ups
# 172 35871103-6cfe-493f-afe1-8401f88fed84 Liegestütz
{'en': '9d756e84-6b77-4f17-b21d-7d266d6a8bd2', 'de': '35871103-6cfe-493f-afe1-8401f88fed84'},
{
'en': '9d756e84-6b77-4f17-b21d-7d266d6a8bd2',
'de': '35871103-6cfe-493f-afe1-8401f88fed84'
},
# 260 36ef5f12-6f77-4754-a926-39915e4b57a5 Decline Pushups
# 721 e386cd27-aef6-4565-88ad-639b0ea04e3a Negativ Pushups
{'en': '36ef5f12-6f77-4754-a926-39915e4b57a5', 'de': 'e386cd27-aef6-4565-88ad-639b0ea04e3a'},
{
'en': '36ef5f12-6f77-4754-a926-39915e4b57a5',
'de': 'e386cd27-aef6-4565-88ad-639b0ea04e3a'
},
# 139 88bbdbde-c9b6-45a1-aee9-efc6f02cfab3 Triceps Machine
# 27 ca03dd79-4a92-47ef-a461-e67ccc406f82 Trizepsmaschine
{'en': '88bbdbde-c9b6-45a1-aee9-efc6f02cfab3', 'de': 'ca03dd79-4a92-47ef-a461-e67ccc406f82'},
{
'en': '88bbdbde-c9b6-45a1-aee9-efc6f02cfab3',
'de': 'ca03dd79-4a92-47ef-a461-e67ccc406f82'
},
# 338 20b88059-3958-4184-9134-b48656ad868d Isometric Wipers
# 724 666d1c77-d74b-411c-be3e-1e60a02c548d Isometric Wipers
{'en': '20b88059-3958-4184-9134-b48656ad868d', 'de': '666d1c77-d74b-411c-be3e-1e60a02c548d'},
{
'en': '20b88059-3958-4184-9134-b48656ad868d',
'de': '666d1c77-d74b-411c-be3e-1e60a02c548d'
},
# 90 20a76bd0-1e56-4a4e-bd79-0ab118552bde Triceps Extensions on Cable With Bar
# 63 60aab5bf-ff7a-4e50-9e92-1f4813c3da75 Trizeps Seildrücken Mit Stange
{'en': '20a76bd0-1e56-4a4e-bd79-0ab118552bde', 'de': '60aab5bf-ff7a-4e50-9e92-1f4813c3da75'},
{
'en': '20a76bd0-1e56-4a4e-bd79-0ab118552bde',
'de': '60aab5bf-ff7a-4e50-9e92-1f4813c3da75'
},
# 89 f1b5e525-6232-4d60-a243-9b2cd6c55298 Triceps Extensions on Cable
# 1 b83e3d85-a53d-4939-a61c-7baa2e94d358 Trizeps Seildrücken
{'en': 'f1b5e525-6232-4d60-a243-9b2cd6c55298', 'de': 'b83e3d85-a53d-4939-a61c-7baa2e94d358'},
{
'en': 'f1b5e525-6232-4d60-a243-9b2cd6c55298',
'de': 'b83e3d85-a53d-4939-a61c-7baa2e94d358'
},
# 270 625aefd5-7ba2-40e9-bdc3-7d3ab1bcf3b8 Pause Bench
# 725 5009eedc-554f-43ef-852e-77ba45a7297c Pause Bench
{'en': '625aefd5-7ba2-40e9-bdc3-7d3ab1bcf3b8', 'de': '5009eedc-554f-43ef-852e-77ba45a7297c'},
{
'en': '625aefd5-7ba2-40e9-bdc3-7d3ab1bcf3b8',
'de': '5009eedc-554f-43ef-852e-77ba45a7297c'
},
# 149 79a3a34c-262f-4cae-b827-b5a85b11b860 Lateral Raises on Cable, One Armed
# 132 382731d2-ae07-4a3c-a055-09dadd5f12e0 Seitheben am Kabel, Einarmig
{'en': '79a3a34c-262f-4cae-b827-b5a85b11b860', 'de': '382731d2-ae07-4a3c-a055-09dadd5f12e0'},
{
'en': '79a3a34c-262f-4cae-b827-b5a85b11b860',
'de': '382731d2-ae07-4a3c-a055-09dadd5f12e0'
},
# 148 5345766a-c092-457a-aa21-8ee6ffa855d4 Lateral Raises
# 20 72e78f4d-65f7-4ddd-9247-cdc1e133fa80 Seitheben KH
{'en': '5345766a-c092-457a-aa21-8ee6ffa855d4', 'de': '72e78f4d-65f7-4ddd-9247-cdc1e133fa80'},
{
'en': '5345766a-c092-457a-aa21-8ee6ffa855d4',
'de': '72e78f4d-65f7-4ddd-9247-cdc1e133fa80'
},
# 191 f2e563d2-507b-4586-88c8-77652cd19648 Front Squats
# 390 d23d1980-3a50-4d3f-8123-90d7e55c7804 Front Kniebeuge
{'en': 'f2e563d2-507b-4586-88c8-77652cd19648', 'de': 'd23d1980-3a50-4d3f-8123-90d7e55c7804'},
{
'en': 'f2e563d2-507b-4586-88c8-77652cd19648',
'de': 'd23d1980-3a50-4d3f-8123-90d7e55c7804'
},
# 229 89e6d2ea-9a17-4a77-9a52-d5bcf39d57fd Military Press
# 153 47c33837-be38-4ebb-b19a-84c280f5f2b0 Frontdrücken LH
{'en': '89e6d2ea-9a17-4a77-9a52-d5bcf39d57fd', 'de': '47c33837-be38-4ebb-b19a-84c280f5f2b0'},
{
'en': '89e6d2ea-9a17-4a77-9a52-d5bcf39d57fd',
'de': '47c33837-be38-4ebb-b19a-84c280f5f2b0'
},
# 854 37097633-de80-4271-9b7c-291f359e0ef4 Hip Thrust
# 230 981ef1f0-414a-478b-bc1b-9c6afa45bc77 Beckenheben
{'en': '37097633-de80-4271-9b7c-291f359e0ef4', 'de': '981ef1f0-414a-478b-bc1b-9c6afa45bc77'},
{
'en': '37097633-de80-4271-9b7c-291f359e0ef4',
'de': '981ef1f0-414a-478b-bc1b-9c6afa45bc77'
},
# 177 a24a0521-6391-419c-bd89-795bba0eb5ee Leg Extension
# 133 da7fccca-941e-457a-a2eb-d0c56d419938 Beinbeuger Sitzend
{'en': 'a24a0521-6391-419c-bd89-795bba0eb5ee', 'de': 'da7fccca-941e-457a-a2eb-d0c56d419938'},
{
'en': 'a24a0521-6391-419c-bd89-795bba0eb5ee',
'de': 'da7fccca-941e-457a-a2eb-d0c56d419938'
},
# 109 f82c579e-c069-4dc7-8e36-a3266dfd8e4a Bent Over Rowing
# 59 126d719a-4b59-4458-b182-d578cdcfee1a Rudern Vorgebeugt LH
{'en': 'f82c579e-c069-4dc7-8e36-a3266dfd8e4a', 'de': '126d719a-4b59-4458-b182-d578cdcfee1a'},
{
'en': 'f82c579e-c069-4dc7-8e36-a3266dfd8e4a',
'de': '126d719a-4b59-4458-b182-d578cdcfee1a'
},
# 108 57747eb3-411a-4efd-8842-a45e562320ee Rowing, Seated
# 245 788ef0a5-492f-48a7-87dc-b15dda185bb8 Rudern Eng Zum Bauch
{'en': '57747eb3-411a-4efd-8842-a45e562320ee', 'de': '788ef0a5-492f-48a7-87dc-b15dda185bb8'},
{
'en': '57747eb3-411a-4efd-8842-a45e562320ee',
'de': '788ef0a5-492f-48a7-87dc-b15dda185bb8'
},
# 119 9926e18f-4e2b-4c20-9477-9bfb08d229bc Shoulder Press, Barbell
# 266 197600e7-9bb2-448c-baca-244d679e7b07 Schulterdrücken LH
{'en': '9926e18f-4e2b-4c20-9477-9bfb08d229bc', 'de': '197600e7-9bb2-448c-baca-244d679e7b07'},
{
'en': '9926e18f-4e2b-4c20-9477-9bfb08d229bc',
'de': '197600e7-9bb2-448c-baca-244d679e7b07'
},
# 123 1df6a1b5-7bd2-402f-9d5e-94f9ed6d8b54 Shoulder Press, Dumbbells
# 241 0d4390ea-51dc-42dc-94af-ba0e94a73484 Schulterdrücken KH
{'en': '1df6a1b5-7bd2-402f-9d5e-94f9ed6d8b54', 'de': '0d4390ea-51dc-42dc-94af-ba0e94a73484'},
{
'en': '1df6a1b5-7bd2-402f-9d5e-94f9ed6d8b54',
'de': '0d4390ea-51dc-42dc-94af-ba0e94a73484'
},
# 107 7ce6b090-5099-4cd0-83ae-1a02725c868b Pull-ups
# 36 4e741c73-d40a-4fad-b0e0-76edd9bbe8df Klimmzüge
{'en': '7ce6b090-5099-4cd0-83ae-1a02725c868b', 'de': '4e741c73-d40a-4fad-b0e0-76edd9bbe8df'},
{
'en': '7ce6b090-5099-4cd0-83ae-1a02725c868b',
'de': '4e741c73-d40a-4fad-b0e0-76edd9bbe8df'
},
# 140 d46adbda-7c60-42a0-b1fd-9ec111b35956 Pull Ups on Machine
# 48 22897ebe-cf17-44cf-97e6-87566285684d Klimmzüge an Maschine
{'en': 'd46adbda-7c60-42a0-b1fd-9ec111b35956', 'de': '22897ebe-cf17-44cf-97e6-87566285684d'},
{
'en': 'd46adbda-7c60-42a0-b1fd-9ec111b35956',
'de': '22897ebe-cf17-44cf-97e6-87566285684d'
},
# 87 d147a6c2-ce64-424b-baf3-4ca841a51512 Dumbbells on Scott Machine
# 28 d7c553b1-e84d-4dbc-9f6c-05db489b27c8 KH an Scottmaschine
{'en': 'd147a6c2-ce64-424b-baf3-4ca841a51512', 'de': 'd7c553b1-e84d-4dbc-9f6c-05db489b27c8'},
{
'en': 'd147a6c2-ce64-424b-baf3-4ca841a51512',
'de': 'd7c553b1-e84d-4dbc-9f6c-05db489b27c8'
},
# 100 b72ae8d4-ede6-4480-8fc5-7b80e369f7ed Decline Bench Press Barbell
# 17 3ef8a516-d0d4-4078-9b4a-d7783da0fcf7 Negativ Bankdrücken
{'en': 'b72ae8d4-ede6-4480-8fc5-7b80e369f7ed', 'de': '3ef8a516-d0d4-4078-9b4a-d7783da0fcf7'},
{
'en': 'b72ae8d4-ede6-4480-8fc5-7b80e369f7ed',
'de': '3ef8a516-d0d4-4078-9b4a-d7783da0fcf7'
},
# 101 80d318b3-4b8a-41aa-9c6c-0a2a921fe1e6 Decline Bench Press Dumbbell
# 720 341c73d5-2e9a-4f13-89c9-c984c86cc088 Negativ Bankdrücken KH
{'en': '80d318b3-4b8a-41aa-9c6c-0a2a921fe1e6', 'de': '341c73d5-2e9a-4f13-89c9-c984c86cc088'},
{
'en': '80d318b3-4b8a-41aa-9c6c-0a2a921fe1e6',
'de': '341c73d5-2e9a-4f13-89c9-c984c86cc088'
},
# 318 bd07fc6b-db86-4139-b6de-e328cea0f694 Turkish Get-Up
# 717 1546af08-017c-4de5-b13f-cb3a1999733d Turkish Get-Up
{'en': 'bd07fc6b-db86-4139-b6de-e328cea0f694', 'de': '1546af08-017c-4de5-b13f-cb3a1999733d'},
{
'en': 'bd07fc6b-db86-4139-b6de-e328cea0f694',
'de': '1546af08-017c-4de5-b13f-cb3a1999733d'
},
# 154 cd4fac32-48fb-4237-a263-a44c5108790a Leg Curls (laying)
# 22 8f0170a2-5274-4487-a295-1a9baf2c92a3 Beinbeuger Liegend
{'en': 'cd4fac32-48fb-4237-a263-a44c5108790a', 'de': '8f0170a2-5274-4487-a295-1a9baf2c92a3'},
{
'en': 'cd4fac32-48fb-4237-a263-a44c5108790a',
'de': '8f0170a2-5274-4487-a295-1a9baf2c92a3'
},
# 118 89c98117-dd53-4334-bfa6-72a96525629a Leg Curls (standing)
# 72 9075241c-0493-4f2a-a399-a57e3634093e Beinbeuger Stehend
{'en': '89c98117-dd53-4334-bfa6-72a96525629a', 'de': '9075241c-0493-4f2a-a399-a57e3634093e'},
{
'en': '89c98117-dd53-4334-bfa6-72a96525629a',
'de': '9075241c-0493-4f2a-a399-a57e3634093e'
},
# 263 9f622058-85a4-4272-ad99-e5696e772137 Roman Chair
# 714 68f96d3e-9172-468c-9018-06f71a97faff Beinheben am Roman Chair
{'en': '9f622058-85a4-4272-ad99-e5696e772137', 'de': '68f96d3e-9172-468c-9018-06f71a97faff'},
{
'en': '9f622058-85a4-4272-ad99-e5696e772137',
'de': '68f96d3e-9172-468c-9018-06f71a97faff'
},
# 421 170cc52f-345f-41b3-bdae-8a5e0c9aa449 Bent-over Lateral Raises
# 47 e701de98-32c6-4e9d-956d-434cf5bcaaf0 Vorgebeugtes Seitheben
{'en': '170cc52f-345f-41b3-bdae-8a5e0c9aa449', 'de': 'e701de98-32c6-4e9d-956d-434cf5bcaaf0'},
{
'en': '170cc52f-345f-41b3-bdae-8a5e0c9aa449',
'de': 'e701de98-32c6-4e9d-956d-434cf5bcaaf0'
},
# 185 bdb7bdbb-8930-46e5-8b98-eb13e604553f Squat Jumps
# 296 032a38cf-b15a-4761-b684-577e41893f54 Tiefe Hocksprünge
{'en': 'bdb7bdbb-8930-46e5-8b98-eb13e604553f', 'de': '032a38cf-b15a-4761-b684-577e41893f54'},
{
'en': 'bdb7bdbb-8930-46e5-8b98-eb13e604553f',
'de': '032a38cf-b15a-4761-b684-577e41893f54'
},
]
@@ -384,7 +652,7 @@ def create_exercise_mapping(apps, schema_editor):
pass
# print(exercise_group[lang], "does not exist")
if(len(exercise_objects) > 0):
if (len(exercise_objects) > 0):
exercise_base_main = exercise_objects[0].exercise_base
for exercise in exercise_objects[1:]:
ExerciseBase.objects.get(id=exercise.exercise_base.id).delete()

View File

@@ -19,21 +19,25 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='exercisebase',
name='license_author',
field=models.CharField(blank=True,
help_text='If you are not the author, enter the name or source '
'here. This is needed for some licenses e.g. the '
'CC-BY-SA.',
max_length=50,
null=True,
verbose_name='Author'),
field=models.CharField(
blank=True,
help_text='If you are not the author, enter the name or source '
'here. This is needed for some licenses e.g. the '
'CC-BY-SA.',
max_length=50,
null=True,
verbose_name='Author'
),
),
migrations.AlterField(
model_name='exercisebase',
name='variations',
field=models.ForeignKey(blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
to='exercises.variation',
verbose_name='Variations'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
to='exercises.variation',
verbose_name='Variations'
),
),
]

View File

@@ -17,8 +17,8 @@
# Standard Library
import logging
import uuid
import pathlib
import uuid
# Django
from django.conf import settings
@@ -41,16 +41,15 @@ import bleach
from wger.core.models import Language
from wger.utils.cache import (
delete_template_fragment_cache,
reset_workout_canonical_form
reset_workout_canonical_form,
)
from wger.utils.helpers import smart_capitalize
from wger.utils.managers import SubmissionManager
from wger.utils.models import (
AbstractLicenseModel,
AbstractSubmissionModel
AbstractSubmissionModel,
)
logger = logging.getLogger(__name__)
@@ -59,16 +58,20 @@ class Muscle(models.Model):
Muscle an exercise works out
"""
name = models.CharField(max_length=50,
verbose_name=_('Name'),
help_text=_('In latin, e.g. "Pectoralis major"'))
name = models.CharField(
max_length=50,
verbose_name=_('Name'),
help_text=_('In latin, e.g. "Pectoralis major"'),
)
# Whether to use the front or the back image for background
is_front = models.BooleanField(default=1)
# Metaclass to set some other properties
class Meta:
ordering = ["name", ]
ordering = [
"name",
]
# Image to use when displaying this as a main muscle in an exercise
@property
@@ -98,14 +101,18 @@ class Equipment(models.Model):
Equipment used or needed by an exercise
"""
name = models.CharField(max_length=50,
verbose_name=_('Name'))
name = models.CharField(
max_length=50,
verbose_name=_('Name'),
)
class Meta:
"""
Set default ordering
"""
ordering = ["name", ]
ordering = [
"name",
]
def __str__(self):
"""
@@ -124,13 +131,17 @@ class ExerciseCategory(models.Model):
"""
Model for an exercise category
"""
name = models.CharField(max_length=100,
verbose_name=_('Name'),)
name = models.CharField(
max_length=100,
verbose_name=_('Name'),
)
# Metaclass to set some other properties
class Meta:
verbose_name_plural = _("Exercise Categories")
ordering = ["name", ]
ordering = [
"name",
]
def __str__(self):
"""
@@ -194,31 +205,41 @@ class ExerciseBase(AbstractSubmissionModel, AbstractLicenseModel, models.Model):
uuid = models.UUIDField(default=uuid.uuid4, editable=False, verbose_name='UUID')
"""Globally unique ID, to identify the base across installations"""
category = models.ForeignKey(ExerciseCategory,
verbose_name=_('Category'),
on_delete=models.CASCADE)
category = models.ForeignKey(
ExerciseCategory,
verbose_name=_('Category'),
on_delete=models.CASCADE,
)
muscles = models.ManyToManyField(Muscle,
blank=True,
verbose_name=_('Primary muscles'))
muscles = models.ManyToManyField(
Muscle,
blank=True,
verbose_name=_('Primary muscles'),
)
"""Main muscles trained by the exercise"""
muscles_secondary = models.ManyToManyField(Muscle,
verbose_name=_('Secondary muscles'),
related_name='secondary_muscles_base',
blank=True)
muscles_secondary = models.ManyToManyField(
Muscle,
verbose_name=_('Secondary muscles'),
related_name='secondary_muscles_base',
blank=True,
)
"""Secondary muscles trained by the exercise"""
equipment = models.ManyToManyField(Equipment,
verbose_name=_('Equipment'),
blank=True)
equipment = models.ManyToManyField(
Equipment,
verbose_name=_('Equipment'),
blank=True,
)
"""Equipment needed by this exercise"""
variations = models.ForeignKey(Variation,
verbose_name=_('Variations'),
on_delete=models.CASCADE,
null=True,
blank=True)
variations = models.ForeignKey(
Variation,
verbose_name=_('Variations'),
on_delete=models.CASCADE,
null=True,
blank=True,
)
"""Variations of this exercise"""
#
@@ -241,40 +262,53 @@ class Exercise(AbstractSubmissionModel, AbstractLicenseModel, models.Model):
objects = SubmissionManager()
"""Custom manager"""
description = models.TextField(max_length=2000,
verbose_name=_('Description'),
validators=[MinLengthValidator(40)])
description = models.TextField(
max_length=2000,
verbose_name=_('Description'),
validators=[MinLengthValidator(40)],
)
"""Description on how to perform the exercise"""
name = models.CharField(max_length=200,
verbose_name=_('Name'))
name = models.CharField(max_length=200, verbose_name=_('Name'))
"""The exercise's name, with correct uppercase"""
name_original = models.CharField(max_length=200,
verbose_name=_('Name'),
default='')
name_original = models.CharField(
max_length=200,
verbose_name=_('Name'),
default='',
)
"""The exercise's name, as entered by the user"""
creation_date = models.DateField(_('Date'),
auto_now_add=True,
null=True,
blank=True)
creation_date = models.DateField(
_('Date'),
auto_now_add=True,
null=True,
blank=True,
)
"""The submission date"""
language = models.ForeignKey(Language,
verbose_name=_('Language'),
on_delete=models.CASCADE)
language = models.ForeignKey(
Language,
verbose_name=_('Language'),
on_delete=models.CASCADE,
)
"""The exercise's language"""
uuid = models.UUIDField(default=uuid.uuid4, editable=False, verbose_name='UUID')
uuid = models.UUIDField(
default=uuid.uuid4,
editable=False,
verbose_name='UUID',
)
"""Globally unique ID, to identify the exercise across installations"""
exercise_base = models.ForeignKey(ExerciseBase,
verbose_name='ExerciseBase',
on_delete=models.CASCADE,
default=None,
null=True,
related_name='exercises')
exercise_base = models.ForeignKey(
ExerciseBase,
verbose_name='ExerciseBase',
on_delete=models.CASCADE,
default=None,
null=True,
related_name='exercises',
)
""" Refers to the base exercise with non translated information """
#
@@ -282,7 +316,9 @@ class Exercise(AbstractSubmissionModel, AbstractLicenseModel, models.Model):
#
class Meta:
base_manager_name = 'objects'
ordering = ["name", ]
ordering = [
"name",
]
def get_absolute_url(self):
"""
@@ -404,14 +440,15 @@ class Exercise(AbstractSubmissionModel, AbstractLicenseModel, models.Model):
context = {
'exercise': self.name,
'url': url,
'site': Site.objects.get_current().domain
'site': Site.objects.get_current().domain,
}
message = render_to_string('exercise/email_new.tpl', context)
mail.send_mail(subject,
message,
settings.WGER_SETTINGS['EMAIL_FROM'],
[user.email],
fail_silently=True)
mail.send_mail(
subject,
message,
settings.WGER_SETTINGS['EMAIL_FROM'], [user.email],
fail_silently=True
)
def set_author(self, request):
"""
@@ -429,11 +466,13 @@ class Exercise(AbstractSubmissionModel, AbstractLicenseModel, models.Model):
subject = _('New user submitted exercise')
message = _('The user {0} submitted a new exercise "{1}".').format(
request.user.username, self.name_original)
mail.mail_admins(str(subject),
str(message),
fail_silently=True)
message = _('The user {0} submitted a new exercise "{1}".'
).format(request.user.username, self.name_original)
mail.mail_admins(
str(subject),
str(message),
fail_silently=True,
)
def exercise_image_upload_dir(instance, filename):
@@ -452,27 +491,37 @@ class ExerciseImage(AbstractSubmissionModel, AbstractLicenseModel, models.Model)
objects = SubmissionManager()
"""Custom manager"""
uuid = models.UUIDField(default=uuid.uuid4,
editable=False,
verbose_name='UUID')
uuid = models.UUIDField(
default=uuid.uuid4,
editable=False,
verbose_name='UUID',
)
"""Globally unique ID, to identify the image across installations"""
exercise_base = models.ForeignKey(ExerciseBase,
verbose_name=_('Exercise'),
on_delete=models.CASCADE)
exercise_base = models.ForeignKey(
ExerciseBase,
verbose_name=_('Exercise'),
on_delete=models.CASCADE,
)
"""The exercise the image belongs to"""
image = models.ImageField(verbose_name=_('Image'),
help_text=_('Only PNG and JPEG formats are supported'),
upload_to=exercise_image_upload_dir)
image = models.ImageField(
verbose_name=_('Image'),
help_text=_('Only PNG and JPEG formats are supported'),
upload_to=exercise_image_upload_dir,
)
"""Uploaded image"""
is_main = models.BooleanField(verbose_name=_('Main picture'),
default=False,
help_text=_("Tick the box if you want to set this image as the "
"main one for the exercise (will be shown e.g. in "
"the search). The first image is automatically "
"marked by the system."))
is_main = models.BooleanField(
verbose_name=_('Main picture'),
default=False,
help_text=_(
"Tick the box if you want to set this image as the "
"main one for the exercise (will be shown e.g. in "
"the search). The first image is automatically "
"marked by the system."
)
)
"""A flag indicating whether the image is the exercise's main image"""
class Meta:
@@ -556,26 +605,30 @@ class ExerciseImage(AbstractSubmissionModel, AbstractLicenseModel, models.Model)
self.license_author = request.user.username
subject = _('New user submitted image')
message = _('The user {0} submitted a new image "{1}" for exercise {2}.').format(
request.user.username,
self.name,
self.exercise)
mail.mail_admins(str(subject),
str(message),
fail_silently=True)
message = _('The user {0} submitted a new image "{1}" for exercise {2}.'
).format(request.user.username, self.name, self.exercise)
mail.mail_admins(
str(subject),
str(message),
fail_silently=True,
)
class ExerciseComment(models.Model):
"""
Model for an exercise comment
"""
exercise = models.ForeignKey(Exercise,
verbose_name=_('Exercise'),
editable=False,
on_delete=models.CASCADE)
comment = models.CharField(max_length=200,
verbose_name=_('Comment'),
help_text=_('A comment about how to correctly do this exercise.'))
exercise = models.ForeignKey(
Exercise,
verbose_name=_('Exercise'),
editable=False,
on_delete=models.CASCADE,
)
comment = models.CharField(
max_length=200,
verbose_name=_('Comment'),
help_text=_('A comment about how to correctly do this exercise.')
)
def __str__(self):
"""

View File

@@ -14,11 +14,10 @@
#
# You should have received a copy of the GNU Affero General Public License
# Django
from django.db.models.signals import (
post_delete,
pre_save
pre_save,
)
from django.dispatch import receiver

View File

@@ -24,7 +24,7 @@ from wger.core.tests.base_testcase import (
WgerAddTestCase,
WgerDeleteTestCase,
WgerEditTestCase,
WgerTestCase
WgerTestCase,
)
from wger.exercises.models import ExerciseCategory
@@ -48,17 +48,19 @@ class CategoryOverviewTestCase(WgerAccessTestCase):
url = 'exercise:category:list'
anonymous_fail = True
user_success = 'admin'
user_fail = ('manager1',
'manager2'
'general_manager1',
'manager3',
'manager4',
'test',
'member1',
'member2',
'member3',
'member4',
'member5')
user_fail = (
'manager1',
'manager2'
'general_manager1',
'manager3',
'manager4',
'test',
'member1',
'member2',
'member3',
'member4',
'member5',
)
class DeleteExerciseCategoryTestCase(WgerDeleteTestCase):

View File

@@ -31,12 +31,15 @@ class ExercisesCorrectionTestCase(WgerTestCase):
Helper function
"""
description = 'a nice, long and accurate description for the exercise'
response = self.client.post(reverse('exercise:exercise:correct', kwargs={'pk': 1}),
{'name_original': 'my test exercise',
'license': 2,
'description': description,
'muscles': [3],
'category': 3})
response = self.client.post(
reverse('exercise:exercise:correct', kwargs={'pk': 1}), {
'name_original': 'my test exercise',
'license': 2,
'description': description,
'muscles': [3],
'category': 3
}
)
if fail:
self.assertEqual(response.status_code, 403)

View File

@@ -24,11 +24,11 @@ from wger.core.tests.base_testcase import (
WgerAddTestCase,
WgerDeleteTestCase,
WgerEditTestCase,
WgerTestCase
WgerTestCase,
)
from wger.exercises.models import (
Equipment,
Exercise
Exercise,
)
from wger.utils.constants import PAGINATION_OBJECTS_PER_PAGE

View File

@@ -21,7 +21,7 @@ from django.core.cache import cache
from django.core.cache.utils import make_template_fragment_key
from django.template import (
Context,
Template
Template,
)
from django.urls import reverse
@@ -30,12 +30,12 @@ from wger.core.tests import api_base_test
from wger.core.tests.base_testcase import (
STATUS_CODES_FAIL,
WgerDeleteTestCase,
WgerTestCase
WgerTestCase,
)
from wger.exercises.models import (
Exercise,
ExerciseCategory,
Muscle
Muscle,
)
from wger.utils.cache import cache_mapper
from wger.utils.constants import WORKOUT_TAB
@@ -244,13 +244,16 @@ class ExercisesTestCase(WgerTestCase):
# Add an exercise
count_before = Exercise.objects.count()
response = self.client.post(reverse('exercise:exercise:add'),
{'name_original': random_string(),
'license': 1,
'exercise_base': {
'category': 2,
'muscles': [1, 2]}
})
response = self.client.post(
reverse('exercise:exercise:add'), {
'name_original': random_string(),
'license': 1,
'exercise_base': {
'category': 2,
'muscles': [1, 2]
}
}
)
count_after = Exercise.objects.count()
self.assertIn(response.status_code, STATUS_CODES_FAIL)
@@ -280,11 +283,14 @@ class ExercisesTestCase(WgerTestCase):
to an existing exercise fails
"""
count_before = Exercise.objects.count()
response = self.client.post(reverse('exercise:exercise:add'),
{'category': 2,
'name_original': 'Squats',
'license': 1,
'muscles': [1, 2]})
response = self.client.post(
reverse('exercise:exercise:add'), {
'category': 2,
'name_original': 'Squats',
'license': 1,
'muscles': [1, 2]
}
)
count_after = Exercise.objects.count()
self.assertIn(response.status_code, STATUS_CODES_FAIL)
@@ -300,12 +306,15 @@ class ExercisesTestCase(WgerTestCase):
count_before = Exercise.objects.count()
description = 'a nice, long and accurate description for the exercise'
name_original = random_string()
response = self.client.post(reverse('exercise:exercise:add'),
{'name_original': name_original,
'license': 1,
'description': description,
'category': 2,
'muscles': [1, 2]})
response = self.client.post(
reverse('exercise:exercise:add'), {
'name_original': name_original,
'license': 1,
'description': description,
'category': 2,
'muscles': [1, 2]
}
)
count_after = Exercise.objects.count()
self.assertEqual(response.status_code, 302)
new_location = response['Location']
@@ -333,46 +342,54 @@ class ExercisesTestCase(WgerTestCase):
self.assertEqual(exercise_1.name, name_original)
# Wrong category - adding
response = self.client.post(reverse('exercise:exercise:add'),
{'category': 111,
'name_original': random_string(),
'license': 1,
'category': 111,
'muscles': [1, 2]
})
response = self.client.post(
reverse('exercise:exercise:add'), {
'category': 111,
'name_original': random_string(),
'license': 1,
'category': 111,
'muscles': [1, 2]
}
)
self.assertTrue(response.context['form'].errors['category'])
# Wrong category - editing
response = self.client.post(reverse('exercise:exercise:edit', kwargs={'pk': '1'}),
{'category': 111,
'name_original': random_string(),
'license': 1,
'category': 111,
'muscles': [1, 2]
})
response = self.client.post(
reverse('exercise:exercise:edit', kwargs={'pk': '1'}), {
'category': 111,
'name_original': random_string(),
'license': 1,
'category': 111,
'muscles': [1, 2]
}
)
if admin:
self.assertTrue(response.context['form'].errors['category'])
else:
self.assertIn(response.status_code, STATUS_CODES_FAIL)
# No muscles - adding
response = self.client.post(reverse('exercise:exercise:add'),
{'category': 1,
'name_original': random_string(),
'license': 1,
'category': 1,
'muscles': []
})
response = self.client.post(
reverse('exercise:exercise:add'), {
'category': 1,
'name_original': random_string(),
'license': 1,
'category': 1,
'muscles': []
}
)
self.assertEqual(response.status_code, 302)
# No muscles - editing
response = self.client.post(reverse('exercise:exercise:edit', kwargs={'pk': '1'}),
{'category': 1,
'name_original': random_string(),
'license': 1,
'category': 1,
'muscles': []
})
response = self.client.post(
reverse('exercise:exercise:edit', kwargs={'pk': '1'}), {
'category': 1,
'name_original': random_string(),
'license': 1,
'category': 1,
'muscles': []
}
)
if admin:
self.assertEqual(response.status_code, 302)
else:
@@ -399,8 +416,7 @@ class ExercisesTestCase(WgerTestCase):
"""
# 1 hit, "Very cool exercise"
response = self.client.get(reverse('exercise-search'),
{'term': 'cool'})
response = self.client.get(reverse('exercise-search'), {'term': 'cool'})
self.assertEqual(response.status_code, 200)
result = json.loads(response.content.decode('utf8'))
self.assertEqual(len(result), 1)
@@ -411,8 +427,7 @@ class ExercisesTestCase(WgerTestCase):
self.assertEqual(result['suggestions'][0]['data']['image_thumbnail'], None)
# 0 hits, "Pending exercise"
response = self.client.get(reverse('exercise-search'),
{'term': 'Pending'})
response = self.client.get(reverse('exercise-search'), {'term': 'Pending'})
self.assertEqual(response.status_code, 200)
result = json.loads(response.content.decode('utf8'))
self.assertEqual(len(result['suggestions']), 0)
@@ -533,10 +548,7 @@ class MuscleTemplateTagTest(WgerTestCase):
"""
context = Context({'muscles': Muscle.objects.get(pk=2)})
template = Template(
'{% load wger_extras %}'
'{% render_muscles muscles %}'
)
template = Template('{% load wger_extras %}' '{% render_muscles muscles %}')
rendered_template = template.render(context)
self.assertIn('images/muscles/main/muscle-2.svg', rendered_template)
self.assertNotIn('images/muscles/secondary/', rendered_template)
@@ -547,12 +559,8 @@ class MuscleTemplateTagTest(WgerTestCase):
Test that the tag works when giben main muscles and empty secondary ones
"""
context = Context({"muscles": Muscle.objects.get(pk=2),
"muscles_sec": []})
template = Template(
'{% load wger_extras %}'
'{% render_muscles muscles muscles_sec %}'
)
context = Context({"muscles": Muscle.objects.get(pk=2), "muscles_sec": []})
template = Template('{% load wger_extras %}' '{% render_muscles muscles muscles_sec %}')
rendered_template = template.render(context)
self.assertIn('images/muscles/main/muscle-2.svg', rendered_template)
self.assertNotIn('images/muscles/secondary/', rendered_template)
@@ -564,10 +572,7 @@ class MuscleTemplateTagTest(WgerTestCase):
"""
context = Context({'muscles': Muscle.objects.get(pk=1)})
template = Template(
'{% load wger_extras %}'
'{% render_muscles muscles_sec=muscles %}'
)
template = Template('{% load wger_extras %}' '{% render_muscles muscles_sec=muscles %}')
rendered_template = template.render(context)
self.assertIn('images/muscles/secondary/muscle-1.svg', rendered_template)
self.assertNotIn('images/muscles/main/', rendered_template)
@@ -578,12 +583,8 @@ class MuscleTemplateTagTest(WgerTestCase):
Test that the tag works when given secondary muscles and empty main ones
"""
context = Context({'muscles_sec': Muscle.objects.get(pk=1),
'muscles': []})
template = Template(
'{% load wger_extras %}'
'{% render_muscles muscles muscles_sec %}'
)
context = Context({'muscles_sec': Muscle.objects.get(pk=1), 'muscles': []})
template = Template('{% load wger_extras %}' '{% render_muscles muscles muscles_sec %}')
rendered_template = template.render(context)
self.assertIn('images/muscles/secondary/muscle-1.svg', rendered_template)
self.assertNotIn('images/muscles/main/', rendered_template)
@@ -594,12 +595,8 @@ class MuscleTemplateTagTest(WgerTestCase):
Test that the tag works when given a list for secondary muscles and empty main ones
"""
context = Context({'muscles_sec': Muscle.objects.filter(is_front=True),
'muscles': []})
template = Template(
'{% load wger_extras %}'
'{% render_muscles muscles muscles_sec %}'
)
context = Context({'muscles_sec': Muscle.objects.filter(is_front=True), 'muscles': []})
template = Template('{% load wger_extras %}' '{% render_muscles muscles muscles_sec %}')
rendered_template = template.render(context)
self.assertIn('images/muscles/secondary/muscle-1.svg', rendered_template)
self.assertNotIn('images/muscles/secondary/muscle-2.svg', rendered_template)
@@ -612,12 +609,13 @@ class MuscleTemplateTagTest(WgerTestCase):
Test that the tag works when given a list for main and secondary muscles
"""
context = Context({'muscles_sec': Muscle.objects.filter(id__in=[5, 6]),
'muscles': Muscle.objects.filter(id__in=[1, 4])})
template = Template(
'{% load wger_extras %}'
'{% render_muscles muscles muscles_sec %}'
context = Context(
{
'muscles_sec': Muscle.objects.filter(id__in=[5, 6]),
'muscles': Muscle.objects.filter(id__in=[1, 4])
}
)
template = Template('{% load wger_extras %}' '{% render_muscles muscles muscles_sec %}')
rendered_template = template.render(context)
self.assertIn('images/muscles/main/muscle-1.svg', rendered_template)
self.assertNotIn('images/muscles/main/muscle-2.svg', rendered_template)
@@ -633,12 +631,8 @@ class MuscleTemplateTagTest(WgerTestCase):
Test that the tag works when given empty input
"""
context = Context({'muscles': [],
'muscles_sec': []})
template = Template(
'{% load wger_extras %}'
'{% render_muscles muscles muscles_sec %}'
)
context = Context({'muscles': [], 'muscles_sec': []})
template = Template('{% load wger_extras %}' '{% render_muscles muscles muscles_sec %}')
rendered_template = template.render(context)
self.assertEqual(rendered_template, "\n\n")
@@ -647,10 +641,7 @@ class MuscleTemplateTagTest(WgerTestCase):
Test that the tag works when given no parameters
"""
template = Template(
'{% load wger_extras %}'
'{% render_muscles %}'
)
template = Template('{% load wger_extras %}' '{% render_muscles %}')
rendered_template = template.render(Context({}))
self.assertEqual(rendered_template, "\n\n")
@@ -692,9 +683,9 @@ class WorkoutCacheTestCase(WgerTestCase):
# TODO: fix test, all registered users can upload exercises
class ExerciseApiTestCase(api_base_test.BaseTestCase,
api_base_test.ApiBaseTestCase,
api_base_test.ApiGetTestCase):
class ExerciseApiTestCase(
api_base_test.BaseTestCase, api_base_test.ApiBaseTestCase, api_base_test.ApiGetTestCase
):
"""
Tests the exercise overview resource
"""
@@ -703,9 +694,9 @@ class ExerciseApiTestCase(api_base_test.BaseTestCase,
private_resource = False
class ExerciseInfoApiTestCase(api_base_test.BaseTestCase,
api_base_test.ApiBaseTestCase,
api_base_test.ApiGetTestCase):
class ExerciseInfoApiTestCase(
api_base_test.BaseTestCase, api_base_test.ApiBaseTestCase, api_base_test.ApiGetTestCase
):
"""
Tests the exercise info resource
"""

View File

@@ -33,11 +33,13 @@ class ExerciseBaseTestCase(WgerTestCase):
"""
exercise = Exercise.objects.get(pk=1)
base = exercise.exercise_base
self. assertEqual(base.category, exercise.category)
self. assertListEqual(self.get_ids(base.equipment), self.get_ids(exercise.equipment))
self. assertListEqual(self.get_ids(base.muscles), self.get_ids(exercise.muscles))
self. assertListEqual(self.get_ids(base.muscles_secondary),
self.get_ids(exercise.muscles_secondary))
self.assertEqual(base.category, exercise.category)
self.assertListEqual(self.get_ids(base.equipment), self.get_ids(exercise.equipment))
self.assertListEqual(self.get_ids(base.muscles), self.get_ids(exercise.muscles))
self.assertListEqual(
self.get_ids(base.muscles_secondary),
self.get_ids(exercise.muscles_secondary),
)
def test_variations(self):
"""Test that the variations are correctly returned"""

View File

@@ -12,7 +12,6 @@
#
# 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
@@ -22,11 +21,11 @@ from wger.core.tests import api_base_test
from wger.core.tests.base_testcase import (
WgerAddTestCase,
WgerEditTestCase,
WgerTestCase
WgerTestCase,
)
from wger.exercises.models import (
Exercise,
ExerciseComment
ExerciseComment,
)
from wger.utils.cache import cache_mapper
@@ -162,6 +161,8 @@ class ExerciseCommentApiTestCase(api_base_test.ApiBaseResourceTestCase):
pk = 1
resource = ExerciseComment
private_resource = False
data = {"comment": "a cool comment",
"exercise": "1",
"id": 1}
data = {
"comment": "a cool comment",
"exercise": "1",
"id": 1,
}

View File

@@ -22,11 +22,11 @@ from wger.core.tests.base_testcase import (
WgerAddTestCase,
WgerDeleteTestCase,
WgerEditTestCase,
WgerTestCase
WgerTestCase,
)
from wger.exercises.models import (
Exercise,
ExerciseImage
ExerciseImage,
)
@@ -45,10 +45,7 @@ class MainImageTestCase(WgerTestCase):
image = ExerciseImage()
image.exercise_base = exercise.exercise_base
image.status = ExerciseImage.STATUS_ACCEPTED
image.image.save(
db_filename,
File(inFile)
)
image.image.save(db_filename, File(inFile))
image.save()
return image.pk
@@ -116,9 +113,11 @@ class AddExerciseImageTestCase(WgerAddTestCase):
object_class = ExerciseImage
url = reverse('exercise:image:add', kwargs={'exercise_pk': 1})
user_fail = False
data = {'is_main': True,
'image': open('wger/exercises/tests/protestschwein.jpg', 'rb'),
'license': 1}
data = {
'is_main': True,
'image': open('wger/exercises/tests/protestschwein.jpg', 'rb'),
'license': 1
}
class EditExerciseImageTestCase(WgerEditTestCase):
@@ -129,8 +128,7 @@ class EditExerciseImageTestCase(WgerEditTestCase):
object_class = ExerciseImage
url = 'exercise:image:edit'
pk = 2
data = {'is_main': True,
'license': 1}
data = {'is_main': True, 'license': 1}
class DeleteExerciseImageTestCase(WgerDeleteTestCase):
@@ -144,9 +142,11 @@ class DeleteExerciseImageTestCase(WgerDeleteTestCase):
# TODO: add POST and DELETE tests
class ExerciseImagesApiTestCase(api_base_test.BaseTestCase,
api_base_test.ApiBaseTestCase,
api_base_test.ApiGetTestCase):
class ExerciseImagesApiTestCase(
api_base_test.BaseTestCase,
api_base_test.ApiBaseTestCase,
api_base_test.ApiGetTestCase,
):
"""
Tests the exercise image resource
"""

View File

@@ -12,7 +12,6 @@
#
# You should have received a copy of the GNU Affero General Public License
# Django
from django.core.cache import cache
from django.core.cache.utils import make_template_fragment_key
@@ -25,7 +24,7 @@ from wger.core.tests.base_testcase import (
WgerAddTestCase,
WgerDeleteTestCase,
WgerEditTestCase,
WgerTestCase
WgerTestCase,
)
from wger.exercises.models import Muscle
@@ -42,10 +41,11 @@ class MuscleRepresentationTestCase(WgerTestCase):
self.assertEqual("{0}".format(Muscle.objects.get(pk=1)), 'Anterior testoid')
# Check image URL properties
self.assertIn("images/muscles/main/muscle-2.svg",
Muscle.objects.get(pk=2).image_url_main)
self.assertIn("images/muscles/secondary/muscle-1.svg",
Muscle.objects.get(pk=1).image_url_secondary)
self.assertIn("images/muscles/main/muscle-2.svg", Muscle.objects.get(pk=2).image_url_main)
self.assertIn(
"images/muscles/secondary/muscle-1.svg",
Muscle.objects.get(pk=1).image_url_secondary
)
class MuscleAdminOverviewTest(WgerAccessTestCase):
@@ -55,17 +55,19 @@ class MuscleAdminOverviewTest(WgerAccessTestCase):
url = 'exercise:muscle:admin-list'
anonymous_fail = True
user_success = 'admin'
user_fail = ('manager1',
'manager2'
'general_manager1',
'manager3',
'manager4',
'test',
'member1',
'member2',
'member3',
'member4',
'member5')
user_fail = (
'manager1',
'manager2'
'general_manager1',
'manager3',
'manager4',
'test',
'member1',
'member2',
'member3',
'member4',
'member5',
)
class MusclesShareButtonTestCase(WgerTestCase):
@@ -95,8 +97,7 @@ class AddMuscleTestCase(WgerAddTestCase):
object_class = Muscle
url = 'exercise:muscle:add'
data = {'name': 'A new muscle',
'is_front': True}
data = {'name': 'A new muscle', 'is_front': True}
class EditMuscleTestCase(WgerEditTestCase):
@@ -107,8 +108,7 @@ class EditMuscleTestCase(WgerEditTestCase):
object_class = Muscle
url = 'exercise:muscle:edit'
pk = 1
data = {'name': 'The new name',
'is_front': True}
data = {'name': 'The new name', 'is_front': True}
class DeleteMuscleTestCase(WgerDeleteTestCase):
@@ -152,8 +152,7 @@ class MuscleApiTestCase(api_base_test.ApiBaseResourceTestCase):
pk = 1
resource = Muscle
private_resource = False
data = {'name': 'The name',
'is_front': True}
data = {'name': 'The name', 'is_front': True}
def test_get_detail(self):
super().test_get_detail()
@@ -161,7 +160,7 @@ class MuscleApiTestCase(api_base_test.ApiBaseResourceTestCase):
# Check that image URLs are present in response
response = self.client.get(self.url_detail)
response_object = response.json()
self.assertIn("images/muscles/main/muscle-1.svg",
response_object["image_url_main"])
self.assertIn("images/muscles/secondary/muscle-1.svg",
response_object["image_url_secondary"])
self.assertIn("images/muscles/main/muscle-1.svg", response_object["image_url_main"])
self.assertIn(
"images/muscles/secondary/muscle-1.svg", response_object["image_url_secondary"]
)

View File

@@ -19,7 +19,7 @@ from django.urls import reverse
# wger
from wger.core.tests.base_testcase import (
STATUS_CODES_FAIL,
WgerTestCase
WgerTestCase,
)
from wger.exercises.models import Exercise

Some files were not shown because too many files have changed in this diff Show More