mirror of
https://github.com/wger-project/wger.git
synced 2026-02-18 00:17:51 +01:00
Automatically format the code with yapf and isort
This commit is contained in:
7
.github/contributing.md
vendored
7
.github/contributing.md
vendored
@@ -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
|
||||
haven’t heard anything by then, feel free to ping the thread.
|
||||
|
||||
|
||||
16
.github/linters/.flake8
vendored
16
.github/linters/.flake8
vendored
@@ -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
|
||||
7
.github/pull_request_template.md
vendored
7
.github/pull_request_template.md
vendored
@@ -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
|
||||
|
||||
1
.github/workflows/linter.yml
vendored
1
.github/workflows/linter.yml
vendored
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# flake8: noqa
|
||||
|
||||
"""
|
||||
Simple FunkLoad test
|
||||
"""
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# flake8: noqa
|
||||
|
||||
|
||||
# This file is part of wger Workout Manager.
|
||||
#
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
|
||||
14
setup.cfg
14
setup.cfg
@@ -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
|
||||
|
||||
2
setup.py
2
setup.py
@@ -10,7 +10,7 @@
|
||||
# Third Party
|
||||
from setuptools import (
|
||||
find_packages,
|
||||
setup
|
||||
setup,
|
||||
)
|
||||
|
||||
# wger
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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, ),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -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
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -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
|
||||
"""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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"),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -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__)
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
"""
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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'),
|
||||
)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -22,7 +22,7 @@ from wger.core.models import RepetitionUnit
|
||||
from wger.exercises.models import (
|
||||
Equipment,
|
||||
ExerciseCategory,
|
||||
Muscle
|
||||
Muscle,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -3,8 +3,6 @@ from django.core.management.base import BaseCommand
|
||||
|
||||
# wger
|
||||
from wger.core.models import UserProfile
|
||||
|
||||
|
||||
"""
|
||||
List users registered via the API
|
||||
"""
|
||||
|
||||
@@ -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, ),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
]
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
]
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
]
|
||||
|
||||
@@ -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)
|
||||
]
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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={
|
||||
|
||||
@@ -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={
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# flake8: noqa
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
||||
@@ -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'
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -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'
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -25,7 +25,6 @@ from rest_framework.authtoken.models import Token
|
||||
# wger
|
||||
from wger.core.tests.base_testcase import WgerTestCase
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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}):
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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',
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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")),
|
||||
]
|
||||
|
||||
@@ -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__)
|
||||
|
||||
|
||||
|
||||
@@ -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__)
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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__)
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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__)
|
||||
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -21,7 +21,7 @@ from wger.exercises.models import (
|
||||
Exercise,
|
||||
ExerciseCategory,
|
||||
ExerciseComment,
|
||||
Muscle
|
||||
Muscle,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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', )
|
||||
|
||||
@@ -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):
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
]
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -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']
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
@@ -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'
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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'
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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'
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
"""
|
||||
|
||||
@@ -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"""
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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
|
||||
"""
|
||||
|
||||
@@ -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"]
|
||||
)
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user