mirror of
https://github.com/wger-project/wger.git
synced 2026-02-18 00:17:51 +01:00
Initial formatting with ruff
This commit is contained in:
3
.github/workflows/formatter.yml
vendored
3
.github/workflows/formatter.yml
vendored
@@ -16,6 +16,9 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Get dependencies
|
||||
run: pip install ruff isort
|
||||
|
||||
- name: Format the code
|
||||
run: ruff format
|
||||
|
||||
|
||||
@@ -62,11 +62,23 @@ filter_dump(('nutrition.logitem',), 'nutrition_diary.json')
|
||||
filter_dump(('exercises.muscle',), 'muscles.json')
|
||||
filter_dump(('exercises.exercisecategory',), 'categories.json')
|
||||
filter_dump(('exercises.exerciseimage',), 'exercise-images.json')
|
||||
filter_dump(('exercises.exercisebase', 'exercises.variation',), 'exercise-base-data.json')
|
||||
filter_dump(
|
||||
('exercises.exercise', 'exercises.exercisecomment', 'exercises.alias'),
|
||||
'translations.json')
|
||||
filter_dump(('exercises.equipment', 'exercises.equipment',), 'equipment.json')
|
||||
(
|
||||
'exercises.exercisebase',
|
||||
'exercises.variation',
|
||||
),
|
||||
'exercise-base-data.json',
|
||||
)
|
||||
filter_dump(
|
||||
('exercises.exercise', 'exercises.exercisecomment', 'exercises.alias'), 'translations.json'
|
||||
)
|
||||
filter_dump(
|
||||
(
|
||||
'exercises.equipment',
|
||||
'exercises.equipment',
|
||||
),
|
||||
'equipment.json',
|
||||
)
|
||||
|
||||
#
|
||||
# Gym
|
||||
|
||||
@@ -12,6 +12,7 @@ from wger.tasks import (
|
||||
setup_django_environment,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# If user passed the settings flag ignore the default wger settings
|
||||
if not any('--settings' in s for s in sys.argv):
|
||||
|
||||
@@ -21,8 +21,8 @@ import os
|
||||
from celery import Celery
|
||||
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
|
||||
app = Celery("wger")
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'settings')
|
||||
app = Celery('wger')
|
||||
|
||||
# read config from Django settings, the CELERY namespace would make celery
|
||||
# config keys has `CELERY` prefix
|
||||
|
||||
@@ -20,7 +20,7 @@ from django.apps import AppConfig
|
||||
|
||||
class ConfigConfig(AppConfig):
|
||||
name = 'wger.config'
|
||||
verbose_name = "Config"
|
||||
verbose_name = 'Config'
|
||||
|
||||
def ready(self):
|
||||
import wger.config.signals
|
||||
|
||||
@@ -4,7 +4,6 @@ from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('gym', '0001_initial'),
|
||||
('core', '0001_initial'),
|
||||
@@ -18,23 +17,22 @@ class Migration(migrations.Migration):
|
||||
'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.',
|
||||
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
|
||||
)
|
||||
on_delete=models.CASCADE,
|
||||
),
|
||||
),
|
||||
],
|
||||
options={},
|
||||
bases=(models.Model, ),
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='LanguageConfig',
|
||||
@@ -43,15 +41,15 @@ class Migration(migrations.Migration):
|
||||
'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')]
|
||||
)
|
||||
choices=[(b'1', 'Exercises'), (b'2', 'Ingredients')],
|
||||
),
|
||||
),
|
||||
('show', models.BooleanField(default=1)),
|
||||
(
|
||||
@@ -60,8 +58,8 @@ class Migration(migrations.Migration):
|
||||
related_name='language_source',
|
||||
editable=False,
|
||||
to='core.Language',
|
||||
on_delete=models.CASCADE
|
||||
)
|
||||
on_delete=models.CASCADE,
|
||||
),
|
||||
),
|
||||
(
|
||||
'language_target',
|
||||
@@ -69,13 +67,13 @@ class Migration(migrations.Migration):
|
||||
related_name='language_target',
|
||||
editable=False,
|
||||
to='core.Language',
|
||||
on_delete=models.CASCADE
|
||||
)
|
||||
on_delete=models.CASCADE,
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'ordering': ['item', 'language_target'],
|
||||
},
|
||||
bases=(models.Model, ),
|
||||
bases=(models.Model,),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -5,7 +5,6 @@ from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('config', '0001_initial'),
|
||||
]
|
||||
@@ -18,7 +17,9 @@ class Migration(migrations.Migration):
|
||||
choices=[
|
||||
('1', 'Exercises'),
|
||||
('2', 'Ingredients'),
|
||||
], editable=False, max_length=2
|
||||
],
|
||||
editable=False,
|
||||
max_length=2,
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -4,11 +4,12 @@ from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('config', '0002_auto_20190618_1617'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(name='LanguageConfig', ),
|
||||
migrations.DeleteModel(
|
||||
name='LanguageConfig',
|
||||
),
|
||||
]
|
||||
|
||||
@@ -52,7 +52,7 @@ class GymConfig(models.Model):
|
||||
),
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.CASCADE
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
"""
|
||||
Default gym for the wger installation
|
||||
@@ -62,14 +62,13 @@ class GymConfig(models.Model):
|
||||
"""
|
||||
Return a more human-readable representation
|
||||
"""
|
||||
return "Default gym {0}".format(self.default_gym)
|
||||
return 'Default gym {0}'.format(self.default_gym)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
"""
|
||||
Perform additional tasks
|
||||
"""
|
||||
if self.default_gym:
|
||||
|
||||
# All users that have no gym set in the profile are edited
|
||||
UserProfile.objects.filter(gym=None).update(gym=self.default_gym)
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ class GymNameHeaderTestCase(WgerTestCase):
|
||||
"""
|
||||
|
||||
def check_header(self, gym=None):
|
||||
|
||||
response = self.client.get(reverse('core:dashboard'))
|
||||
self.assertEqual(response.context['custom_header'], gym)
|
||||
|
||||
|
||||
@@ -34,6 +34,6 @@ patterns_gym_config = [
|
||||
urlpatterns = [
|
||||
path(
|
||||
'gym-config/',
|
||||
include((patterns_gym_config, 'gym_config'), namespace="gym_config"),
|
||||
include((patterns_gym_config, 'gym_config'), namespace='gym_config'),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -34,6 +34,7 @@ class GymConfigUpdateView(WgerFormMixin, UpdateView):
|
||||
"""
|
||||
Generic view to edit the gym config table
|
||||
"""
|
||||
|
||||
model = GymConfig
|
||||
fields = ['default_gym']
|
||||
permission_required = 'config.change_gymconfig'
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
LANGUAGE_ENDPOINT = "language"
|
||||
LICENSE_ENDPOINT = "license"
|
||||
LANGUAGE_ENDPOINT = 'language'
|
||||
LICENSE_ENDPOINT = 'license'
|
||||
|
||||
@@ -46,9 +46,9 @@ class UserprofileSerializer(serializers.ModelSerializer):
|
||||
Workout session serializer
|
||||
"""
|
||||
|
||||
email = serializers.EmailField(source="user.email", read_only=True)
|
||||
username = serializers.EmailField(source="user.username", read_only=True)
|
||||
date_joined = serializers.EmailField(source="user.date_joined", read_only=True)
|
||||
email = serializers.EmailField(source='user.email', read_only=True)
|
||||
username = serializers.EmailField(source='user.username', read_only=True)
|
||||
date_joined = serializers.EmailField(source='user.date_joined', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = UserProfile
|
||||
@@ -86,7 +86,8 @@ class UserprofileSerializer(serializers.ModelSerializer):
|
||||
|
||||
|
||||
class UserLoginSerializer(serializers.ModelSerializer):
|
||||
""" Serializer to map to User model in relation to api user"""
|
||||
"""Serializer to map to User model in relation to api user"""
|
||||
|
||||
email = serializers.CharField(required=False)
|
||||
username = serializers.CharField(required=False)
|
||||
password = serializers.CharField(required=True, min_length=8)
|
||||
@@ -102,9 +103,9 @@ class UserLoginSerializer(serializers.ModelSerializer):
|
||||
super().__init__(instance, data, **kwargs)
|
||||
|
||||
def validate(self, data):
|
||||
email = data.get("email", None)
|
||||
username = data.get("username", None)
|
||||
password = data.get("password", None)
|
||||
email = data.get('email', None)
|
||||
username = data.get('username', None)
|
||||
password = data.get('password', None)
|
||||
|
||||
if email is None and username is None:
|
||||
raise serializers.ValidationError('Please provide an "email" or a "username"')
|
||||
@@ -122,9 +123,10 @@ class UserLoginSerializer(serializers.ModelSerializer):
|
||||
|
||||
class UserRegistrationSerializer(serializers.ModelSerializer):
|
||||
email = serializers.EmailField(
|
||||
required=False, validators=[
|
||||
required=False,
|
||||
validators=[
|
||||
UniqueValidator(queryset=User.objects.all()),
|
||||
]
|
||||
],
|
||||
)
|
||||
username = serializers.CharField(
|
||||
required=True,
|
||||
|
||||
@@ -88,6 +88,7 @@ class UserProfileViewSet(viewsets.ModelViewSet):
|
||||
returns the data for the currently logged-in user's profile. To update
|
||||
the profile, use a POST request with the new data, not a PATCH.
|
||||
"""
|
||||
|
||||
serializer_class = UserprofileSerializer
|
||||
permission_classes = (
|
||||
IsAuthenticated,
|
||||
@@ -99,7 +100,7 @@ class UserProfileViewSet(viewsets.ModelViewSet):
|
||||
Only allow access to appropriate objects
|
||||
"""
|
||||
# REST API generation
|
||||
if getattr(self, "swagger_fake_view", False):
|
||||
if getattr(self, 'swagger_fake_view', False):
|
||||
return UserProfile.objects.none()
|
||||
|
||||
return UserProfile.objects.filter(user=self.request.user)
|
||||
@@ -159,10 +160,7 @@ class UserProfileViewSet(viewsets.ModelViewSet):
|
||||
|
||||
send_email(request.user)
|
||||
return Response(
|
||||
{
|
||||
'status': 'sent',
|
||||
'message': f'A verification email was sent to {request.user.email}'
|
||||
}
|
||||
{'status': 'sent', 'message': f'A verification email was sent to {request.user.email}'}
|
||||
)
|
||||
|
||||
|
||||
@@ -170,7 +168,8 @@ class ApplicationVersionView(viewsets.ViewSet):
|
||||
"""
|
||||
Returns the application's version
|
||||
"""
|
||||
permission_classes = (AllowAny, )
|
||||
|
||||
permission_classes = (AllowAny,)
|
||||
|
||||
@staticmethod
|
||||
@extend_schema(
|
||||
@@ -187,7 +186,8 @@ class PermissionView(viewsets.ViewSet):
|
||||
"""
|
||||
Checks whether the user has a django permission
|
||||
"""
|
||||
permission_classes = (AllowAny, )
|
||||
|
||||
permission_classes = (AllowAny,)
|
||||
|
||||
@staticmethod
|
||||
@extend_schema(
|
||||
@@ -200,12 +200,13 @@ class PermissionView(viewsets.ViewSet):
|
||||
),
|
||||
],
|
||||
responses={
|
||||
201:
|
||||
inline_serializer(name='PermissionResponse', fields={
|
||||
'result': BooleanField(),
|
||||
}),
|
||||
400:
|
||||
OpenApiResponse(
|
||||
201: inline_serializer(
|
||||
name='PermissionResponse',
|
||||
fields={
|
||||
'result': BooleanField(),
|
||||
},
|
||||
),
|
||||
400: OpenApiResponse(
|
||||
description="Please pass a permission name in the 'permission' parameter"
|
||||
),
|
||||
},
|
||||
@@ -230,7 +231,8 @@ class RequiredApplicationVersionView(viewsets.ViewSet):
|
||||
Returns the minimum required version of flutter app to access this server
|
||||
such as 1.4.2 or 3.0.0
|
||||
"""
|
||||
permission_classes = (AllowAny, )
|
||||
|
||||
permission_classes = (AllowAny,)
|
||||
|
||||
@staticmethod
|
||||
@extend_schema(
|
||||
@@ -250,7 +252,8 @@ class UserAPILoginView(viewsets.ViewSet):
|
||||
|
||||
Note that it is recommended to use token authorization instead.
|
||||
"""
|
||||
permission_classes = (AllowAny, )
|
||||
|
||||
permission_classes = (AllowAny,)
|
||||
queryset = User.objects.all()
|
||||
serializer_class = UserLoginSerializer
|
||||
throttle_scope = 'login'
|
||||
@@ -261,12 +264,11 @@ class UserAPILoginView(viewsets.ViewSet):
|
||||
@extend_schema(
|
||||
parameters=[],
|
||||
responses={
|
||||
status.HTTP_200_OK:
|
||||
inline_serializer(
|
||||
status.HTTP_200_OK: inline_serializer(
|
||||
name='loginSerializer',
|
||||
fields={'token': CharField()},
|
||||
),
|
||||
}
|
||||
},
|
||||
)
|
||||
def post(self, request):
|
||||
serializer = self.serializer_class(data=request.data, request=request)
|
||||
@@ -292,8 +294,8 @@ class UserAPILoginView(viewsets.ViewSet):
|
||||
data={'token': token.key},
|
||||
status=status.HTTP_200_OK,
|
||||
headers={
|
||||
"Deprecation": "Sat, 01 Oct 2022 23:59:59 GMT",
|
||||
}
|
||||
'Deprecation': 'Sat, 01 Oct 2022 23:59:59 GMT',
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -301,7 +303,8 @@ class UserAPIRegistrationViewSet(viewsets.ViewSet):
|
||||
"""
|
||||
API endpoint
|
||||
"""
|
||||
permission_classes = (AllowRegisterUser, )
|
||||
|
||||
permission_classes = (AllowRegisterUser,)
|
||||
serializer_class = UserRegistrationSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
@@ -313,12 +316,11 @@ class UserAPIRegistrationViewSet(viewsets.ViewSet):
|
||||
@extend_schema(
|
||||
parameters=[],
|
||||
responses={
|
||||
status.HTTP_200_OK:
|
||||
inline_serializer(
|
||||
status.HTTP_200_OK: inline_serializer(
|
||||
name='loginSerializer',
|
||||
fields={'token': CharField()},
|
||||
),
|
||||
}
|
||||
},
|
||||
)
|
||||
def post(self, request):
|
||||
data = request.data
|
||||
@@ -333,11 +335,8 @@ class UserAPIRegistrationViewSet(viewsets.ViewSet):
|
||||
send_email(user)
|
||||
|
||||
return Response(
|
||||
{
|
||||
'message': 'api user successfully registered',
|
||||
'token': token.key
|
||||
},
|
||||
status=status.HTTP_201_CREATED
|
||||
{'message': 'api user successfully registered', 'token': token.key},
|
||||
status=status.HTTP_201_CREATED,
|
||||
)
|
||||
|
||||
|
||||
@@ -345,6 +344,7 @@ class LanguageViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
"""
|
||||
API endpoint for the languages used in the application
|
||||
"""
|
||||
|
||||
queryset = Language.objects.all()
|
||||
serializer_class = LanguageSerializer
|
||||
ordering_fields = '__all__'
|
||||
@@ -361,16 +361,18 @@ class DaysOfWeekViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
|
||||
This has historical reasons, and it's better and easier to just define a simple enum
|
||||
"""
|
||||
|
||||
queryset = DaysOfWeek.objects.all()
|
||||
serializer_class = DaysOfWeekSerializer
|
||||
ordering_fields = '__all__'
|
||||
filterset_fields = ('day_of_week', )
|
||||
filterset_fields = ('day_of_week',)
|
||||
|
||||
|
||||
class LicenseViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
"""
|
||||
API endpoint for license objects
|
||||
"""
|
||||
|
||||
queryset = License.objects.all()
|
||||
serializer_class = LicenseSerializer
|
||||
ordering_fields = '__all__'
|
||||
@@ -385,17 +387,19 @@ class RepetitionUnitViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
"""
|
||||
API endpoint for repetition units objects
|
||||
"""
|
||||
|
||||
queryset = RepetitionUnit.objects.all()
|
||||
serializer_class = RepetitionUnitSerializer
|
||||
ordering_fields = '__all__'
|
||||
filterset_fields = ('name', )
|
||||
filterset_fields = ('name',)
|
||||
|
||||
|
||||
class RoutineWeightUnitViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
"""
|
||||
API endpoint for weight units objects
|
||||
"""
|
||||
|
||||
queryset = WeightUnit.objects.all()
|
||||
serializer_class = RoutineWeightUnitSerializer
|
||||
ordering_fields = '__all__'
|
||||
filterset_fields = ('name', )
|
||||
filterset_fields = ('name',)
|
||||
|
||||
@@ -18,7 +18,7 @@ from django.apps import AppConfig
|
||||
|
||||
class CoreConfig(AppConfig):
|
||||
name = 'wger.core'
|
||||
verbose_name = "Core"
|
||||
verbose_name = 'Core'
|
||||
|
||||
def ready(self):
|
||||
import wger.core.signals
|
||||
|
||||
@@ -122,7 +122,7 @@ def create_demo_entries(user):
|
||||
workout=workout,
|
||||
reps=reps,
|
||||
weight=18 - reps + random.randint(1, 4),
|
||||
date=datetime.date.today() - datetime.timedelta(weeks=i)
|
||||
date=datetime.date.today() - datetime.timedelta(weeks=i),
|
||||
)
|
||||
weight_log.append(log)
|
||||
|
||||
@@ -142,7 +142,7 @@ def create_demo_entries(user):
|
||||
workout=workout,
|
||||
reps=reps,
|
||||
weight=30 - reps + random.randint(1, 4),
|
||||
date=datetime.date.today() - datetime.timedelta(weeks=i)
|
||||
date=datetime.date.today() - datetime.timedelta(weeks=i),
|
||||
)
|
||||
weight_log.append(log)
|
||||
|
||||
@@ -162,7 +162,7 @@ def create_demo_entries(user):
|
||||
workout=workout,
|
||||
reps=reps,
|
||||
weight=110 - reps + random.randint(1, 10),
|
||||
date=datetime.date.today() - datetime.timedelta(weeks=i)
|
||||
date=datetime.date.today() - datetime.timedelta(weeks=i),
|
||||
)
|
||||
weight_log.append(log)
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ class UserLoginForm(AuthenticationForm):
|
||||
Row(
|
||||
Column('username', css_class='col-6'),
|
||||
Column('password', css_class='col-6'),
|
||||
css_class='form-row'
|
||||
css_class='form-row',
|
||||
)
|
||||
)
|
||||
|
||||
@@ -89,8 +89,8 @@ class UserLoginForm(AuthenticationForm):
|
||||
return self.cleaned_data
|
||||
|
||||
def authenticate(self, request):
|
||||
username = self.cleaned_data.get("username")
|
||||
password = self.cleaned_data.get("password")
|
||||
username = self.cleaned_data.get('username')
|
||||
password = self.cleaned_data.get('password')
|
||||
|
||||
if username and password:
|
||||
self.user_cache = authenticate(
|
||||
@@ -108,20 +108,20 @@ 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
|
||||
label=_('Email'),
|
||||
help_text=_('Used for password resets and, optionally, e-mail reminders.'),
|
||||
required=False,
|
||||
)
|
||||
birthdate = forms.DateField(
|
||||
label=_("Date of Birth"),
|
||||
label=_('Date of Birth'),
|
||||
required=False,
|
||||
widget=forms.DateInput(
|
||||
attrs={
|
||||
'type': 'date',
|
||||
"max": str(date.today().replace(year=date.today().year - 10)),
|
||||
"min": str(date.today().replace(year=date.today().year - 100))
|
||||
'max': str(date.today().replace(year=date.today().year - 10)),
|
||||
'min': str(date.today().replace(year=date.today().year - 100)),
|
||||
},
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
@@ -145,42 +145,46 @@ class UserPreferencesForm(forms.ModelForm):
|
||||
self.helper.form_class = 'wger-form'
|
||||
self.helper.layout = Layout(
|
||||
Fieldset(
|
||||
_("Personal data"), 'email',
|
||||
_('Personal data'),
|
||||
'email',
|
||||
Row(
|
||||
Column('first_name', css_class='col-6'),
|
||||
Column('last_name', css_class='col-6'),
|
||||
css_class='form-row'
|
||||
), 'birthdate', HTML("<hr>")
|
||||
css_class='form-row',
|
||||
),
|
||||
'birthdate',
|
||||
HTML('<hr>'),
|
||||
),
|
||||
Fieldset(
|
||||
_("Workout reminders"),
|
||||
_('Workout reminders'),
|
||||
'workout_reminder_active',
|
||||
'workout_reminder',
|
||||
'workout_duration',
|
||||
HTML("<hr>"),
|
||||
HTML('<hr>'),
|
||||
),
|
||||
Fieldset(
|
||||
_("Other settings"),
|
||||
"ro_access",
|
||||
"notification_language",
|
||||
"weight_unit",
|
||||
"show_comments",
|
||||
"show_english_ingredients",
|
||||
"num_days_weight_reminder",
|
||||
), ButtonHolder(Submit('submit', _("Save"), css_class='btn-success btn-block'))
|
||||
_('Other settings'),
|
||||
'ro_access',
|
||||
'notification_language',
|
||||
'weight_unit',
|
||||
'show_comments',
|
||||
'show_english_ingredients',
|
||||
'num_days_weight_reminder',
|
||||
),
|
||||
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
|
||||
label=_('Email'),
|
||||
help_text=_('Used for password resets and, optionally, email reminders.'),
|
||||
required=False,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ('email', )
|
||||
fields = ('email',)
|
||||
|
||||
def clean_email(self):
|
||||
"""
|
||||
@@ -192,7 +196,7 @@ class UserEmailForm(forms.ModelForm):
|
||||
we want to check that nobody else has that e-mail address.
|
||||
"""
|
||||
|
||||
email = self.cleaned_data["email"]
|
||||
email = self.cleaned_data['email']
|
||||
if not email:
|
||||
return email
|
||||
try:
|
||||
@@ -203,7 +207,7 @@ class UserEmailForm(forms.ModelForm):
|
||||
except User.DoesNotExist:
|
||||
return email
|
||||
|
||||
raise ValidationError(_("This e-mail address is already in use."))
|
||||
raise ValidationError(_('This e-mail address is already in use.'))
|
||||
|
||||
|
||||
class UserPersonalInformationForm(UserEmailForm):
|
||||
@@ -222,10 +226,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"),
|
||||
label=_('Password'),
|
||||
widget=PasswordInput,
|
||||
help_text=_('Please enter your current password.')
|
||||
help_text=_('Please enter your current password.'),
|
||||
)
|
||||
|
||||
def __init__(self, user, data=None):
|
||||
@@ -234,7 +239,7 @@ class PasswordConfirmationForm(Form):
|
||||
self.helper = FormHelper()
|
||||
self.helper.layout = Layout(
|
||||
'password',
|
||||
ButtonHolder(Submit('submit', _("Delete"), css_class='btn-danger btn-block'))
|
||||
ButtonHolder(Submit('submit', _('Delete'), css_class='btn-danger btn-block')),
|
||||
)
|
||||
|
||||
def clean_password(self):
|
||||
@@ -244,7 +249,7 @@ class PasswordConfirmationForm(Form):
|
||||
password = self.cleaned_data.get('password', None)
|
||||
if not self.user.check_password(password):
|
||||
raise ValidationError(_('Invalid password'))
|
||||
return self.cleaned_data.get("password")
|
||||
return self.cleaned_data.get('password')
|
||||
|
||||
|
||||
class RegistrationForm(UserCreationForm, UserEmailForm):
|
||||
@@ -263,13 +268,15 @@ class RegistrationForm(UserCreationForm, UserEmailForm):
|
||||
self.helper = FormHelper()
|
||||
self.helper.form_class = 'wger-form'
|
||||
self.helper.layout = Layout(
|
||||
'username', 'email',
|
||||
'username',
|
||||
'email',
|
||||
Row(
|
||||
Column('password1', css_class='col-md-6 col-12'),
|
||||
Column('password2', css_class='col-md-6 col-12'),
|
||||
css_class='form-row'
|
||||
), 'captcha',
|
||||
ButtonHolder(Submit('submitBtn', _("Register"), css_class='btn-success btn-block'))
|
||||
css_class='form-row',
|
||||
),
|
||||
'captcha',
|
||||
ButtonHolder(Submit('submitBtn', _('Register'), css_class='btn-success btn-block')),
|
||||
)
|
||||
|
||||
|
||||
@@ -283,16 +290,17 @@ 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='col-md-6 col-12'),
|
||||
Column('password2', css_class='col-md-6 col-12'),
|
||||
css_class='form-row'
|
||||
css_class='form-row',
|
||||
),
|
||||
ButtonHolder(
|
||||
Submit('submit', _("Register"), css_class='btn-success col-sm-6 col-12'),
|
||||
css_class='text-center'
|
||||
)
|
||||
Submit('submit', _('Register'), css_class='btn-success col-sm-6 col-12'),
|
||||
css_class='text-center',
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -300,12 +308,13 @@ 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
|
||||
required=False,
|
||||
)
|
||||
|
||||
comment = forms.CharField(
|
||||
@@ -314,7 +323,7 @@ class FeedbackRegisteredForm(forms.Form):
|
||||
widget=widgets.Textarea,
|
||||
label=_('Comment'),
|
||||
help_text=_('What do you want to say?'),
|
||||
required=True
|
||||
required=True,
|
||||
)
|
||||
|
||||
|
||||
@@ -322,6 +331,7 @@ class FeedbackAnonymousForm(FeedbackRegisteredForm):
|
||||
"""
|
||||
Feedback form used for anonymous users (has additionally a reCAPTCHA field)
|
||||
"""
|
||||
|
||||
captcha = ReCaptchaField(
|
||||
widget=ReCaptchaV3,
|
||||
label='reCaptcha',
|
||||
|
||||
@@ -9,7 +9,6 @@ Custom command permitting users to create user accounts
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
||||
help = 'Permit user to create user accounts'
|
||||
|
||||
# Named (optional arguments)
|
||||
@@ -32,15 +31,15 @@ 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.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.style.SUCCESS(f"{options['name']} is now ALLOWED to add users via the API")
|
||||
)
|
||||
user.userprofile.save()
|
||||
|
||||
|
||||
@@ -36,17 +36,18 @@ class Command(BaseCommand):
|
||||
Clears caches (HTML, etc.)
|
||||
"""
|
||||
|
||||
help = 'Clears the application cache. You *must* pass an option selecting ' \
|
||||
'what exactly you want to clear. See available options.'
|
||||
help = (
|
||||
'Clears the application cache. You *must* pass an option selecting '
|
||||
'what exactly you want to clear. See available options.'
|
||||
)
|
||||
|
||||
def add_arguments(self, parser):
|
||||
|
||||
parser.add_argument(
|
||||
'--clear-template',
|
||||
action='store_true',
|
||||
dest='clear_template',
|
||||
default=False,
|
||||
help='Clear only template caches'
|
||||
help='Clear only template caches',
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
@@ -54,7 +55,7 @@ class Command(BaseCommand):
|
||||
action='store_true',
|
||||
dest='clear_workout',
|
||||
default=False,
|
||||
help='Clear only the workout canonical view'
|
||||
help='Clear only the workout canonical view',
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
@@ -62,7 +63,7 @@ class Command(BaseCommand):
|
||||
action='store_true',
|
||||
dest='clear_all',
|
||||
default=False,
|
||||
help='Clear ALL cached entries'
|
||||
help='Clear ALL cached entries',
|
||||
)
|
||||
|
||||
def handle(self, **options):
|
||||
@@ -71,7 +72,8 @@ class Command(BaseCommand):
|
||||
"""
|
||||
|
||||
if (
|
||||
not options['clear_template'] and not options['clear_workout']
|
||||
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')
|
||||
@@ -79,20 +81,20 @@ class Command(BaseCommand):
|
||||
# Exercises, cached template fragments
|
||||
if options['clear_template']:
|
||||
if int(options['verbosity']) >= 2:
|
||||
self.stdout.write("*** Clearing templates")
|
||||
self.stdout.write('*** Clearing templates')
|
||||
|
||||
for user in User.objects.all():
|
||||
if int(options['verbosity']) >= 2:
|
||||
self.stdout.write(f"* Processing user {user.username}")
|
||||
self.stdout.write(f'* Processing user {user.username}')
|
||||
|
||||
for entry in WorkoutLog.objects.filter(user=user).dates('date', 'year'):
|
||||
|
||||
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'):
|
||||
self.stdout.write(f' Year {entry.year}')
|
||||
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}")
|
||||
self.stdout.write(f' Month {entry.month}')
|
||||
reset_workout_log(user.id, entry.year, entry.month)
|
||||
for day in WorkoutLog.objects.filter(
|
||||
user=user,
|
||||
@@ -100,7 +102,7 @@ class Command(BaseCommand):
|
||||
date__month=month.month,
|
||||
).dates('date', 'day'):
|
||||
if int(options['verbosity']) >= 3:
|
||||
self.stdout.write(f" Day {day.day}")
|
||||
self.stdout.write(f' Day {day.day}')
|
||||
reset_workout_log(user.id, entry.year, entry.month, day)
|
||||
|
||||
# Workout canonical form
|
||||
|
||||
@@ -31,14 +31,13 @@ class Command(BaseCommand):
|
||||
help = 'Deletes all temporary users older than 1 week'
|
||||
|
||||
def handle(self, **options):
|
||||
|
||||
profile_list = UserProfile.objects.filter(is_temporary=True)
|
||||
counter = 0
|
||||
for profile in profile_list:
|
||||
delta = now() - profile.user.date_joined
|
||||
|
||||
if (delta >= datetime.timedelta(7)):
|
||||
if delta >= datetime.timedelta(7):
|
||||
counter += 1
|
||||
profile.user.delete()
|
||||
|
||||
self.stdout.write(f"Deleted {counter} temporary users")
|
||||
self.stdout.write(f'Deleted {counter} temporary users')
|
||||
|
||||
@@ -44,14 +44,13 @@ class Command(BaseCommand):
|
||||
help = 'Dummy generator for users'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
|
||||
parser.add_argument(
|
||||
'--nr-entries',
|
||||
action='store',
|
||||
default=20,
|
||||
dest='number_users',
|
||||
type=int,
|
||||
help='The number of users to generate (default: 20)'
|
||||
help='The number of users to generate (default: 20)',
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
@@ -60,11 +59,10 @@ class Command(BaseCommand):
|
||||
default='auto',
|
||||
dest='add_to_gym',
|
||||
type=str,
|
||||
help='Gym to assign the users to. Allowed values: auto, none, <gym_id>. Default: auto'
|
||||
help='Gym to assign the users to. Allowed values: auto, none, <gym_id>. Default: auto',
|
||||
)
|
||||
|
||||
def handle(self, **options):
|
||||
|
||||
faker = Faker()
|
||||
|
||||
self.stdout.write(f"** Generating {options['number_users']} users")
|
||||
|
||||
@@ -21,8 +21,8 @@ from wger.exercises.models import Exercise
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = (
|
||||
"Helper command to extract translations for the exercises used in the flutter repo for "
|
||||
"the screenshots for the play store"
|
||||
'Helper command to extract translations for the exercises used in the flutter repo for '
|
||||
'the screenshots for the play store'
|
||||
)
|
||||
|
||||
uuids = {
|
||||
@@ -31,7 +31,7 @@ class Command(BaseCommand):
|
||||
'deadLift': 'ee8e8db4-2d82-49e1-ab7f-891e9a354934',
|
||||
'crunches': 'b186f1f8-4957-44dc-bf30-d0b00064ce6f',
|
||||
'curls': '1ae6a28d-10e7-4ecf-af4f-905f8193e2c6',
|
||||
'raises': '63375f5b-2d81-471c-bea4-fc3d207e96cb'
|
||||
'raises': '63375f5b-2d81-471c-bea4-fc3d207e96cb',
|
||||
}
|
||||
|
||||
def handle(self, **options):
|
||||
@@ -39,7 +39,7 @@ class Command(BaseCommand):
|
||||
languages = []
|
||||
|
||||
for exercise_key in self.uuids.keys():
|
||||
self.stdout.write(f"Extracting translations for {exercise_key}")
|
||||
self.stdout.write(f'Extracting translations for {exercise_key}')
|
||||
|
||||
uuid = self.uuids[exercise_key]
|
||||
translations = Exercise.objects.filter(exercise_base__uuid=uuid)
|
||||
@@ -47,14 +47,14 @@ class Command(BaseCommand):
|
||||
variables = []
|
||||
|
||||
for translation in translations:
|
||||
variable_name = f"{exercise_key}{translation.language.short_name.upper()}"
|
||||
variable_name = f'{exercise_key}{translation.language.short_name.upper()}'
|
||||
variables.append(variable_name)
|
||||
|
||||
if translation.language not in languages:
|
||||
languages.append(translation.language)
|
||||
|
||||
self.stdout.write(
|
||||
f"- translation {translation.language.short_name}: {translation.name}"
|
||||
f'- translation {translation.language.short_name}: {translation.name}'
|
||||
)
|
||||
|
||||
out.append(
|
||||
@@ -70,22 +70,23 @@ class Command(BaseCommand):
|
||||
);
|
||||
"""
|
||||
)
|
||||
self.stdout.write("")
|
||||
self.stdout.write('')
|
||||
|
||||
out.append(f'final {exercise_key}Translations = [{",".join(variables)}];')
|
||||
|
||||
for language in languages:
|
||||
out.insert(
|
||||
0, f"""const tLanguage{language.id} = Language(
|
||||
0,
|
||||
f"""const tLanguage{language.id} = Language(
|
||||
id: {language.id},
|
||||
shortName: '{language.short_name}',
|
||||
fullName: '{language.full_name}',
|
||||
);"""
|
||||
);""",
|
||||
)
|
||||
|
||||
out.insert(0, "import 'package:wger/models/exercises/language.dart';")
|
||||
out.insert(0, "import 'package:wger/models/exercises/translation.dart';")
|
||||
out.insert(0, "// Autogenerated by extract-i18n-flutter-exercises.py do not edit!")
|
||||
out.insert(0, '// Autogenerated by extract-i18n-flutter-exercises.py do not edit!')
|
||||
|
||||
#
|
||||
# Write to output
|
||||
|
||||
@@ -38,22 +38,25 @@ class Command(BaseCommand):
|
||||
help = 'Write the translatable strings from the database to a file'
|
||||
|
||||
def handle(self, **options):
|
||||
|
||||
# Replace whitespace and other problematic characters with underscores
|
||||
def cleanup_name(text: str) -> str:
|
||||
return text.lower(). \
|
||||
replace(' ', '_'). \
|
||||
replace('-', '_'). \
|
||||
replace('/', '_'). \
|
||||
replace('(', '_'). \
|
||||
replace(')', '_')
|
||||
return (
|
||||
text.lower()
|
||||
.replace(' ', '_')
|
||||
.replace('-', '_')
|
||||
.replace('/', '_')
|
||||
.replace('(', '_')
|
||||
.replace(')', '_')
|
||||
)
|
||||
|
||||
# Collect all translatable items
|
||||
data = [i for i in ExerciseCategory.objects.all()] \
|
||||
+ [i for i in Equipment.objects.all()] \
|
||||
+ [i.name_en for i in Muscle.objects.all() if i.name_en] \
|
||||
+ [i for i in RepetitionUnit.objects.all()] \
|
||||
+ [i for i in WeightUnit.objects.all()]
|
||||
data = (
|
||||
[i for i in ExerciseCategory.objects.all()]
|
||||
+ [i for i in Equipment.objects.all()]
|
||||
+ [i.name_en for i in Muscle.objects.all() if i.name_en]
|
||||
+ [i for i in RepetitionUnit.objects.all()]
|
||||
+ [i for i in WeightUnit.objects.all()]
|
||||
)
|
||||
|
||||
# Make entries unique and sort alphabetically
|
||||
data = sorted(set([i.__str__() for i in data]))
|
||||
@@ -70,7 +73,7 @@ class Command(BaseCommand):
|
||||
#
|
||||
# React - copy the file to src/i18n.tsx in the React repo
|
||||
with open('wger/i18n.tsx', 'w') as f:
|
||||
out = '''
|
||||
out = """
|
||||
// This code is autogenerated in the backend repo in extract-i18n.py do not edit!
|
||||
|
||||
// Translate dynamic strings that are returned from the server
|
||||
@@ -82,13 +85,13 @@ class Command(BaseCommand):
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const DummyComponent = () => {
|
||||
const [t] = useTranslation();'''
|
||||
const [t] = useTranslation();"""
|
||||
for i in data:
|
||||
out += f't("server.{cleanup_name(i.__str__())}");\n'
|
||||
|
||||
out += '''
|
||||
out += """
|
||||
return (<p></p>);
|
||||
};'''
|
||||
};"""
|
||||
f.write(out)
|
||||
self.stdout.write(self.style.SUCCESS(f'Wrote content to wger/i18n.tsx'))
|
||||
|
||||
@@ -106,7 +109,7 @@ class Command(BaseCommand):
|
||||
|
||||
# Copy to lib/helpers/i18n.dart in the flutter repo
|
||||
with open('wger/i18n.dart', 'w') as f:
|
||||
out = '''
|
||||
out = """
|
||||
/// This code is autogenerated in the backend repo in extract-i18n.py do not edit!
|
||||
|
||||
/// Translate dynamic strings that are returned from the server
|
||||
@@ -121,19 +124,19 @@ class Command(BaseCommand):
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
String getTranslation(String value, BuildContext context) {
|
||||
switch (value) {'''
|
||||
switch (value) {"""
|
||||
for i in data:
|
||||
out += f'''
|
||||
out += f"""
|
||||
case '{i}':
|
||||
return AppLocalizations.of(context).{cleanup_name(i.__str__())};
|
||||
'''
|
||||
"""
|
||||
|
||||
out += '''
|
||||
out += """
|
||||
default:
|
||||
log('Could not translate the server string $value', level: Level.WARNING.value);
|
||||
return value;
|
||||
}
|
||||
}'''
|
||||
}"""
|
||||
|
||||
f.write(out)
|
||||
self.stdout.write(self.style.SUCCESS('Wrote content to wger/i18n.dart'))
|
||||
|
||||
@@ -11,13 +11,11 @@ List users registered via the API
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
||||
help = 'List all users registered via REST, grouped by app-user'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
|
||||
for app_profile in UserProfile.objects.filter(can_add_user=True):
|
||||
self.stdout.write(self.style.SUCCESS(f"Users created by {app_profile.user.username}:"))
|
||||
self.stdout.write(self.style.SUCCESS(f'Users created by {app_profile.user.username}:'))
|
||||
|
||||
for user_profile in UserProfile.objects.filter(added_by=app_profile.user):
|
||||
self.stdout.write(f"- {user_profile.user.username}")
|
||||
self.stdout.write(f'- {user_profile.user.username}')
|
||||
|
||||
@@ -35,4 +35,4 @@ class Command(BaseCommand):
|
||||
site.domain = domain
|
||||
site.name = settings.SITE_URL
|
||||
site.save()
|
||||
self.stdout.write(self.style.SUCCESS(f"Set site URL to {domain}"))
|
||||
self.stdout.write(self.style.SUCCESS(f'Set site URL to {domain}'))
|
||||
|
||||
@@ -25,9 +25,11 @@ class Command(BaseCommand):
|
||||
Updates the user cache table
|
||||
"""
|
||||
|
||||
help = 'Update the user cache-table. This is only needed when the python' \
|
||||
'code used to calculate any of the cached entries is changed and ' \
|
||||
'the ones in the database need to be updated to reflect the new logic.'
|
||||
help = (
|
||||
'Update the user cache-table. This is only needed when the python'
|
||||
'code used to calculate any of the cached entries is changed and '
|
||||
'the ones in the database need to be updated to reflect the new logic.'
|
||||
)
|
||||
|
||||
def handle(self, **options):
|
||||
"""
|
||||
|
||||
@@ -6,7 +6,6 @@ import django.core.validators
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('gym', '0001_initial'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
@@ -20,14 +19,14 @@ class Migration(migrations.Migration):
|
||||
'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',
|
||||
@@ -36,7 +35,7 @@ class Migration(migrations.Migration):
|
||||
'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')),
|
||||
@@ -44,7 +43,7 @@ class Migration(migrations.Migration):
|
||||
options={
|
||||
'ordering': ['full_name'],
|
||||
},
|
||||
bases=(models.Model, ),
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='License',
|
||||
@@ -53,12 +52,12 @@ class Migration(migrations.Migration):
|
||||
'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')
|
||||
models.CharField(max_length=15, verbose_name='Short name, e.g. CC-BY-SA 3'),
|
||||
),
|
||||
(
|
||||
'url',
|
||||
@@ -66,14 +65,14 @@ class Migration(migrations.Migration):
|
||||
help_text='Link to license text or other information',
|
||||
null=True,
|
||||
verbose_name='Link',
|
||||
blank=True
|
||||
)
|
||||
blank=True,
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'ordering': ['full_name'],
|
||||
},
|
||||
bases=(models.Model, ),
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='UserProfile',
|
||||
@@ -82,7 +81,7 @@ class Migration(migrations.Migration):
|
||||
'id',
|
||||
models.AutoField(
|
||||
verbose_name='ID', serialize=False, auto_created=True, primary_key=True
|
||||
)
|
||||
),
|
||||
),
|
||||
('is_temporary', models.BooleanField(default=False, editable=False)),
|
||||
(
|
||||
@@ -90,52 +89,48 @@ class Migration(migrations.Migration):
|
||||
models.BooleanField(
|
||||
default=True,
|
||||
help_text='Check to show exercise comments on the workout view',
|
||||
verbose_name='Show exercise comments'
|
||||
)
|
||||
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'
|
||||
)
|
||||
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'
|
||||
)
|
||||
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.',
|
||||
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)
|
||||
]
|
||||
)
|
||||
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.',
|
||||
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)
|
||||
]
|
||||
)
|
||||
django.core.validators.MaxValueValidator(30),
|
||||
],
|
||||
),
|
||||
),
|
||||
('last_workout_notification', models.DateField(null=True, editable=False)),
|
||||
(
|
||||
@@ -143,21 +138,20 @@ class Migration(migrations.Migration):
|
||||
models.BooleanField(
|
||||
default=True,
|
||||
help_text='Check to activate timer pauses between exercises.',
|
||||
verbose_name='Use pauses in workout timer'
|
||||
)
|
||||
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.',
|
||||
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)
|
||||
]
|
||||
)
|
||||
django.core.validators.MaxValueValidator(400),
|
||||
],
|
||||
),
|
||||
),
|
||||
(
|
||||
'age',
|
||||
@@ -167,9 +161,9 @@ class Migration(migrations.Migration):
|
||||
verbose_name='Age',
|
||||
validators=[
|
||||
django.core.validators.MinValueValidator(10),
|
||||
django.core.validators.MaxValueValidator(100)
|
||||
]
|
||||
)
|
||||
django.core.validators.MaxValueValidator(100),
|
||||
],
|
||||
),
|
||||
),
|
||||
(
|
||||
'height',
|
||||
@@ -179,9 +173,9 @@ class Migration(migrations.Migration):
|
||||
verbose_name='Height (cm)',
|
||||
validators=[
|
||||
django.core.validators.MinValueValidator(140),
|
||||
django.core.validators.MaxValueValidator(230)
|
||||
]
|
||||
)
|
||||
django.core.validators.MaxValueValidator(230),
|
||||
],
|
||||
),
|
||||
),
|
||||
(
|
||||
'gender',
|
||||
@@ -189,8 +183,8 @@ class Migration(migrations.Migration):
|
||||
default=b'1',
|
||||
max_length=1,
|
||||
null=True,
|
||||
choices=[(b'1', 'Male'), (b'2', 'Female')]
|
||||
)
|
||||
choices=[(b'1', 'Male'), (b'2', 'Female')],
|
||||
),
|
||||
),
|
||||
(
|
||||
'sleep_hours',
|
||||
@@ -201,9 +195,9 @@ class Migration(migrations.Migration):
|
||||
verbose_name='Hours of sleep',
|
||||
validators=[
|
||||
django.core.validators.MinValueValidator(4),
|
||||
django.core.validators.MaxValueValidator(10)
|
||||
]
|
||||
)
|
||||
django.core.validators.MaxValueValidator(10),
|
||||
],
|
||||
),
|
||||
),
|
||||
(
|
||||
'work_hours',
|
||||
@@ -214,9 +208,9 @@ class Migration(migrations.Migration):
|
||||
verbose_name='Work',
|
||||
validators=[
|
||||
django.core.validators.MinValueValidator(1),
|
||||
django.core.validators.MaxValueValidator(15)
|
||||
]
|
||||
)
|
||||
django.core.validators.MaxValueValidator(15),
|
||||
],
|
||||
),
|
||||
),
|
||||
(
|
||||
'work_intensity',
|
||||
@@ -226,8 +220,8 @@ class Migration(migrations.Migration):
|
||||
max_length=1,
|
||||
help_text='Approximately',
|
||||
null=True,
|
||||
verbose_name='Physical intensity'
|
||||
)
|
||||
verbose_name='Physical intensity',
|
||||
),
|
||||
),
|
||||
(
|
||||
'sport_hours',
|
||||
@@ -238,9 +232,9 @@ class Migration(migrations.Migration):
|
||||
verbose_name='Sport',
|
||||
validators=[
|
||||
django.core.validators.MinValueValidator(1),
|
||||
django.core.validators.MaxValueValidator(30)
|
||||
]
|
||||
)
|
||||
django.core.validators.MaxValueValidator(30),
|
||||
],
|
||||
),
|
||||
),
|
||||
(
|
||||
'sport_intensity',
|
||||
@@ -250,8 +244,8 @@ class Migration(migrations.Migration):
|
||||
max_length=1,
|
||||
help_text='Approximately',
|
||||
null=True,
|
||||
verbose_name='Physical intensity'
|
||||
)
|
||||
verbose_name='Physical intensity',
|
||||
),
|
||||
),
|
||||
(
|
||||
'freetime_hours',
|
||||
@@ -262,9 +256,9 @@ class Migration(migrations.Migration):
|
||||
verbose_name='Free time',
|
||||
validators=[
|
||||
django.core.validators.MinValueValidator(1),
|
||||
django.core.validators.MaxValueValidator(15)
|
||||
]
|
||||
)
|
||||
django.core.validators.MaxValueValidator(15),
|
||||
],
|
||||
),
|
||||
),
|
||||
(
|
||||
'freetime_intensity',
|
||||
@@ -274,8 +268,8 @@ class Migration(migrations.Migration):
|
||||
max_length=1,
|
||||
help_text='Approximately',
|
||||
null=True,
|
||||
verbose_name='Physical intensity'
|
||||
)
|
||||
verbose_name='Physical intensity',
|
||||
),
|
||||
),
|
||||
(
|
||||
'calories',
|
||||
@@ -286,9 +280,9 @@ class Migration(migrations.Migration):
|
||||
verbose_name='Total daily calories',
|
||||
validators=[
|
||||
django.core.validators.MinValueValidator(1500),
|
||||
django.core.validators.MaxValueValidator(5000)
|
||||
]
|
||||
)
|
||||
django.core.validators.MaxValueValidator(5000),
|
||||
],
|
||||
),
|
||||
),
|
||||
(
|
||||
'weight_unit',
|
||||
@@ -296,8 +290,8 @@ class Migration(migrations.Migration):
|
||||
default=b'kg',
|
||||
max_length=2,
|
||||
verbose_name='Weight unit',
|
||||
choices=[(b'kg', 'Metric (kilogram)'), (b'lb', 'Imperial (pound)')]
|
||||
)
|
||||
choices=[(b'kg', 'Metric (kilogram)'), (b'lb', 'Imperial (pound)')],
|
||||
),
|
||||
),
|
||||
(
|
||||
'gym',
|
||||
@@ -306,8 +300,8 @@ class Migration(migrations.Migration):
|
||||
editable=False,
|
||||
to='gym.Gym',
|
||||
null=True,
|
||||
on_delete=models.CASCADE
|
||||
)
|
||||
on_delete=models.CASCADE,
|
||||
),
|
||||
),
|
||||
(
|
||||
'notification_language',
|
||||
@@ -315,19 +309,18 @@ class Migration(migrations.Migration):
|
||||
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
|
||||
)
|
||||
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, ),
|
||||
bases=(models.Model,),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -4,7 +4,6 @@ from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0001_initial'),
|
||||
]
|
||||
@@ -14,10 +13,9 @@ class Migration(migrations.Migration):
|
||||
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.',
|
||||
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
|
||||
default=False,
|
||||
),
|
||||
preserve_default=True,
|
||||
),
|
||||
@@ -30,7 +28,7 @@ class Migration(migrations.Migration):
|
||||
max_length=1,
|
||||
help_text='Approximately',
|
||||
default='1',
|
||||
verbose_name='Physical intensity'
|
||||
verbose_name='Physical intensity',
|
||||
),
|
||||
preserve_default=True,
|
||||
),
|
||||
@@ -51,7 +49,7 @@ class Migration(migrations.Migration):
|
||||
max_length=1,
|
||||
help_text='Approximately',
|
||||
default='2',
|
||||
verbose_name='Physical intensity'
|
||||
verbose_name='Physical intensity',
|
||||
),
|
||||
preserve_default=True,
|
||||
),
|
||||
@@ -62,7 +60,7 @@ class Migration(migrations.Migration):
|
||||
choices=[('kg', 'Metric (kilogram)'), ('lb', 'Imperial (pound)')],
|
||||
verbose_name='Weight unit',
|
||||
max_length=2,
|
||||
default='kg'
|
||||
default='kg',
|
||||
),
|
||||
preserve_default=True,
|
||||
),
|
||||
@@ -75,7 +73,7 @@ class Migration(migrations.Migration):
|
||||
max_length=1,
|
||||
help_text='Approximately',
|
||||
default='1',
|
||||
verbose_name='Physical intensity'
|
||||
verbose_name='Physical intensity',
|
||||
),
|
||||
preserve_default=True,
|
||||
),
|
||||
|
||||
@@ -5,7 +5,6 @@ import django.core.validators
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0002_auto_20141225_1512'),
|
||||
]
|
||||
@@ -18,7 +17,7 @@ class Migration(migrations.Migration):
|
||||
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)'
|
||||
help_text='Number of days after the last weight entry (enter 0 to deactivate)',
|
||||
),
|
||||
preserve_default=True,
|
||||
),
|
||||
@@ -30,8 +29,8 @@ class Migration(migrations.Migration):
|
||||
null=True,
|
||||
validators=[
|
||||
django.core.validators.MinValueValidator(10),
|
||||
django.core.validators.MaxValueValidator(100)
|
||||
]
|
||||
django.core.validators.MaxValueValidator(100),
|
||||
],
|
||||
),
|
||||
preserve_default=True,
|
||||
),
|
||||
@@ -43,8 +42,8 @@ class Migration(migrations.Migration):
|
||||
null=True,
|
||||
validators=[
|
||||
django.core.validators.MinValueValidator(140),
|
||||
django.core.validators.MaxValueValidator(230)
|
||||
]
|
||||
django.core.validators.MaxValueValidator(230),
|
||||
],
|
||||
),
|
||||
preserve_default=True,
|
||||
),
|
||||
@@ -54,8 +53,7 @@ class Migration(migrations.Migration):
|
||||
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.'
|
||||
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,
|
||||
),
|
||||
|
||||
@@ -4,7 +4,6 @@ from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0003_auto_20150217_1554'),
|
||||
]
|
||||
@@ -17,7 +16,7 @@ class Migration(migrations.Migration):
|
||||
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)'
|
||||
help_text='Number of days after the last weight entry (enter 0 to deactivate)',
|
||||
),
|
||||
preserve_default=True,
|
||||
),
|
||||
|
||||
@@ -6,7 +6,6 @@ from django.conf import settings
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('core', '0004_auto_20150217_1914'),
|
||||
@@ -20,14 +19,14 @@ class Migration(migrations.Migration):
|
||||
'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
|
||||
)
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -40,8 +39,8 @@ class Migration(migrations.Migration):
|
||||
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)
|
||||
]
|
||||
django.core.validators.MaxValueValidator(30),
|
||||
],
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -7,13 +7,12 @@ def create_usercache(apps, schema_editor):
|
||||
"""
|
||||
Creates a usercache table for all users
|
||||
"""
|
||||
User = apps.get_model("auth", "User")
|
||||
Usercache = apps.get_model("core", "Usercache")
|
||||
WorkoutLog = apps.get_model("manager", "WorkoutLog")
|
||||
WorkoutSession = apps.get_model("manager", "WorkoutSession")
|
||||
User = apps.get_model('auth', 'User')
|
||||
Usercache = apps.get_model('core', 'Usercache')
|
||||
WorkoutLog = apps.get_model('manager', 'WorkoutLog')
|
||||
WorkoutSession = apps.get_model('manager', 'WorkoutSession')
|
||||
|
||||
for user in User.objects.all():
|
||||
|
||||
#
|
||||
# This is the logic of get_user_last_activity at the time this migration
|
||||
# was created.
|
||||
@@ -46,13 +45,12 @@ def delete_usercache(apps, schema_editor):
|
||||
"""
|
||||
Deletes the usercache table for all users
|
||||
"""
|
||||
Usercache = apps.get_model("core", "Usercache")
|
||||
Usercache = apps.get_model('core', 'Usercache')
|
||||
for cache in Usercache.objects.all():
|
||||
cache.delete()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0005_auto_20151025_2236'),
|
||||
('auth', '0006_require_contenttypes_0002'),
|
||||
|
||||
@@ -4,7 +4,6 @@ from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0006_auto_20151025_2237'),
|
||||
]
|
||||
@@ -17,7 +16,7 @@ class Migration(migrations.Migration):
|
||||
'id',
|
||||
models.AutoField(
|
||||
verbose_name='ID', serialize=False, primary_key=True, auto_created=True
|
||||
)
|
||||
),
|
||||
),
|
||||
('name', models.CharField(verbose_name='Name', max_length=100)),
|
||||
],
|
||||
|
||||
@@ -4,7 +4,6 @@ from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0007_repetitionunit'),
|
||||
]
|
||||
@@ -17,7 +16,7 @@ class Migration(migrations.Migration):
|
||||
'id',
|
||||
models.AutoField(
|
||||
verbose_name='ID', serialize=False, auto_created=True, primary_key=True
|
||||
)
|
||||
),
|
||||
),
|
||||
('name', models.CharField(verbose_name='Name', max_length=100)),
|
||||
],
|
||||
|
||||
@@ -28,7 +28,6 @@ def insert_data(apps, schema_editor):
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0008_weightunit'),
|
||||
]
|
||||
|
||||
@@ -6,7 +6,6 @@ import wger.core.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0009_auto_20160303_2340'),
|
||||
]
|
||||
@@ -18,17 +17,16 @@ class Migration(migrations.Migration):
|
||||
field=models.DateField(
|
||||
null=True,
|
||||
validators=[wger.core.models.profile.birthdate_validator],
|
||||
verbose_name='Date of Birth'
|
||||
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.',
|
||||
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'
|
||||
verbose_name='Full name',
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -5,7 +5,6 @@ import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('gym', '0008_auto_20190618_1617'),
|
||||
('core', '0010_auto_20170403_0144'),
|
||||
@@ -20,7 +19,7 @@ class Migration(migrations.Migration):
|
||||
editable=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to='gym.gym'
|
||||
to='gym.gym',
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -6,7 +6,6 @@ import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('core', '0011_auto_20201201_0653'),
|
||||
@@ -21,7 +20,7 @@ class Migration(migrations.Migration):
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='added_by',
|
||||
to=settings.AUTH_USER_MODEL
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
|
||||
@@ -4,7 +4,6 @@ from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0012_auto_20210210_1228'),
|
||||
]
|
||||
|
||||
@@ -4,7 +4,6 @@ from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0012_auto_20210210_1228'),
|
||||
]
|
||||
|
||||
@@ -4,7 +4,6 @@ from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0013_auto_20210726_1729'),
|
||||
('core', '0013_userprofile_email_verified'),
|
||||
|
||||
@@ -4,7 +4,6 @@ from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0014_merge_20210818_1735'),
|
||||
]
|
||||
|
||||
@@ -4,7 +4,6 @@ from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0015_alter_language_short_name'),
|
||||
]
|
||||
@@ -14,10 +13,7 @@ class Migration(migrations.Migration):
|
||||
model_name='language',
|
||||
name='short_name',
|
||||
field=models.CharField(
|
||||
help_text='ISO 639-1',
|
||||
max_length=2,
|
||||
unique=True,
|
||||
verbose_name='Language short name'
|
||||
help_text='ISO 639-1', max_length=2, unique=True, verbose_name='Language short name'
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -41,4 +41,4 @@ class UserCache(models.Model):
|
||||
"""
|
||||
Return a more human-readable representation
|
||||
"""
|
||||
return f"Cache for user {self.user}"
|
||||
return f'Cache for user {self.user}'
|
||||
|
||||
@@ -32,8 +32,9 @@ class DaysOfWeek(models.Model):
|
||||
"""
|
||||
Order by day-ID, this is needed for some DBs
|
||||
"""
|
||||
|
||||
ordering = [
|
||||
"pk",
|
||||
'pk',
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
|
||||
@@ -43,8 +43,9 @@ class Language(models.Model):
|
||||
"""
|
||||
Set Meta options
|
||||
"""
|
||||
|
||||
ordering = [
|
||||
"full_name",
|
||||
'full_name',
|
||||
]
|
||||
|
||||
#
|
||||
@@ -54,7 +55,7 @@ class Language(models.Model):
|
||||
"""
|
||||
Return a more human-readable representation
|
||||
"""
|
||||
return f"{self.full_name} ({self.short_name})"
|
||||
return f'{self.full_name} ({self.short_name})'
|
||||
|
||||
def get_absolute_url(self):
|
||||
"""
|
||||
|
||||
@@ -31,7 +31,7 @@ class License(models.Model):
|
||||
'If a license has been localized, e.g. the Creative '
|
||||
'Commons licenses for the different countries, add '
|
||||
'them as separate entries here.'
|
||||
)
|
||||
),
|
||||
)
|
||||
"""Full name"""
|
||||
|
||||
@@ -45,7 +45,7 @@ class License(models.Model):
|
||||
verbose_name=_('Link'),
|
||||
help_text=_('Link to license text or other information'),
|
||||
blank=True,
|
||||
null=True
|
||||
null=True,
|
||||
)
|
||||
"""URL to full license text or other information"""
|
||||
|
||||
@@ -53,8 +53,9 @@ class License(models.Model):
|
||||
"""
|
||||
Set Meta options
|
||||
"""
|
||||
|
||||
ordering = [
|
||||
"full_name",
|
||||
'full_name',
|
||||
]
|
||||
|
||||
#
|
||||
@@ -64,7 +65,7 @@ class License(models.Model):
|
||||
"""
|
||||
Return a more human-readable representation
|
||||
"""
|
||||
return f"{self.full_name} ({self.short_name})"
|
||||
return f'{self.full_name} ({self.short_name})'
|
||||
|
||||
#
|
||||
# Own methods
|
||||
|
||||
@@ -116,8 +116,7 @@ class UserProfile(models.Model):
|
||||
|
||||
show_comments = models.BooleanField(
|
||||
verbose_name=_('Show exercise comments'),
|
||||
help_text=_('Check to show exercise comments on the '
|
||||
'workout view'),
|
||||
help_text=_('Check to show exercise comments on the ' 'workout view'),
|
||||
default=True,
|
||||
)
|
||||
"""
|
||||
@@ -134,7 +133,7 @@ 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
|
||||
default=True,
|
||||
)
|
||||
|
||||
workout_reminder_active = models.BooleanField(
|
||||
@@ -145,15 +144,14 @@ by the US Department of Agriculture. It is extremely complete, with around
|
||||
'to provide a valid email for this '
|
||||
'to work.'
|
||||
),
|
||||
default=False
|
||||
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.'),
|
||||
help_text=_('The number of days you want to be reminded ' 'before a workout expires.'),
|
||||
default=14,
|
||||
validators=[MinValueValidator(1), MaxValueValidator(30)]
|
||||
validators=[MinValueValidator(1), MaxValueValidator(30)],
|
||||
)
|
||||
workout_duration = IntegerField(
|
||||
verbose_name=_('Default duration of workouts'),
|
||||
@@ -163,7 +161,7 @@ by the US Department of Agriculture. It is extremely complete, with around
|
||||
'reminders.'
|
||||
),
|
||||
default=12,
|
||||
validators=[MinValueValidator(1), MaxValueValidator(30)]
|
||||
validators=[MinValueValidator(1), MaxValueValidator(30)],
|
||||
)
|
||||
last_workout_notification = models.DateField(editable=False, blank=False, null=True)
|
||||
"""
|
||||
@@ -183,7 +181,7 @@ by the US Department of Agriculture. It is extremely complete, with around
|
||||
'language used on the website.'
|
||||
),
|
||||
default=2,
|
||||
on_delete=models.CASCADE
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
|
||||
@property
|
||||
@@ -216,7 +214,7 @@ by the US Department of Agriculture. It is extremely complete, with around
|
||||
verbose_name=_('Age'),
|
||||
blank=False,
|
||||
null=True,
|
||||
validators=[MinValueValidator(10), MaxValueValidator(100)]
|
||||
validators=[MinValueValidator(10), MaxValueValidator(100)],
|
||||
)
|
||||
"""The user's age"""
|
||||
|
||||
@@ -232,7 +230,7 @@ by the US Department of Agriculture. It is extremely complete, with around
|
||||
verbose_name=_('Height (cm)'),
|
||||
blank=False,
|
||||
validators=[MinValueValidator(140), MaxValueValidator(230)],
|
||||
null=True
|
||||
null=True,
|
||||
)
|
||||
"""The user's height"""
|
||||
|
||||
@@ -251,7 +249,7 @@ by the US Department of Agriculture. It is extremely complete, with around
|
||||
default=7,
|
||||
blank=False,
|
||||
null=True,
|
||||
validators=[MinValueValidator(4), MaxValueValidator(10)]
|
||||
validators=[MinValueValidator(4), MaxValueValidator(10)],
|
||||
)
|
||||
"""The average hours of sleep per day"""
|
||||
|
||||
@@ -261,7 +259,7 @@ by the US Department of Agriculture. It is extremely complete, with around
|
||||
default=8,
|
||||
blank=False,
|
||||
null=True,
|
||||
validators=[MinValueValidator(1), MaxValueValidator(15)]
|
||||
validators=[MinValueValidator(1), MaxValueValidator(15)],
|
||||
)
|
||||
"""The average hours at work per day"""
|
||||
|
||||
@@ -272,7 +270,7 @@ by the US Department of Agriculture. It is extremely complete, with around
|
||||
choices=INTENSITY,
|
||||
default=INTENSITY_LOW,
|
||||
blank=False,
|
||||
null=True
|
||||
null=True,
|
||||
)
|
||||
"""Physical intensity of work"""
|
||||
|
||||
@@ -282,7 +280,7 @@ by the US Department of Agriculture. It is extremely complete, with around
|
||||
default=3,
|
||||
blank=False,
|
||||
null=True,
|
||||
validators=[MinValueValidator(1), MaxValueValidator(30)]
|
||||
validators=[MinValueValidator(1), MaxValueValidator(30)],
|
||||
)
|
||||
"""The average hours performing sports per week"""
|
||||
|
||||
@@ -293,7 +291,7 @@ by the US Department of Agriculture. It is extremely complete, with around
|
||||
choices=INTENSITY,
|
||||
default=INTENSITY_MEDIUM,
|
||||
blank=False,
|
||||
null=True
|
||||
null=True,
|
||||
)
|
||||
"""Physical intensity of sport activities"""
|
||||
|
||||
@@ -303,7 +301,7 @@ by the US Department of Agriculture. It is extremely complete, with around
|
||||
default=8,
|
||||
blank=False,
|
||||
null=True,
|
||||
validators=[MinValueValidator(1), MaxValueValidator(15)]
|
||||
validators=[MinValueValidator(1), MaxValueValidator(15)],
|
||||
)
|
||||
"""The average hours of free time per day"""
|
||||
|
||||
@@ -314,7 +312,7 @@ by the US Department of Agriculture. It is extremely complete, with around
|
||||
choices=INTENSITY,
|
||||
default=INTENSITY_LOW,
|
||||
blank=False,
|
||||
null=True
|
||||
null=True,
|
||||
)
|
||||
"""Physical intensity during free time"""
|
||||
|
||||
@@ -324,7 +322,7 @@ by the US Department of Agriculture. It is extremely complete, with around
|
||||
default=2500,
|
||||
blank=False,
|
||||
null=True,
|
||||
validators=[MinValueValidator(1500), MaxValueValidator(5000)]
|
||||
validators=[MinValueValidator(1500), MaxValueValidator(5000)],
|
||||
)
|
||||
"""Basic caloric intake based on physical activity"""
|
||||
|
||||
@@ -346,18 +344,15 @@ by the US Department of Agriculture. It is extremely complete, with around
|
||||
'logs in a read-only mode. You need to set this '
|
||||
'before you can share links e.g. to social media.'
|
||||
),
|
||||
default=False
|
||||
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)'),
|
||||
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
|
||||
default=0,
|
||||
)
|
||||
"""Number of Days for email weight reminder"""
|
||||
|
||||
@@ -415,17 +410,16 @@ 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):
|
||||
"""
|
||||
Return a more human-readable representation
|
||||
"""
|
||||
return f"Profile for user {self.user}"
|
||||
return f'Profile for user {self.user}'
|
||||
|
||||
@property
|
||||
def use_metric(self):
|
||||
@@ -521,11 +515,9 @@ 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
|
||||
|
||||
@@ -28,8 +28,9 @@ class RepetitionUnit(models.Model):
|
||||
"""
|
||||
Set Meta options
|
||||
"""
|
||||
|
||||
ordering = [
|
||||
"name",
|
||||
'name',
|
||||
]
|
||||
|
||||
name = models.CharField(max_length=100, verbose_name=_('Name'))
|
||||
|
||||
@@ -28,8 +28,9 @@ class WeightUnit(models.Model):
|
||||
"""
|
||||
Set Meta options
|
||||
"""
|
||||
|
||||
ordering = [
|
||||
"name",
|
||||
'name',
|
||||
]
|
||||
|
||||
name = models.CharField(max_length=100, verbose_name=_('Name'))
|
||||
|
||||
@@ -61,8 +61,7 @@ def set_user_age(sender, instance, **kwargs):
|
||||
today = datetime.date.today()
|
||||
birthday = instance.birthdate
|
||||
instance.age = (
|
||||
today.year - birthday.year -
|
||||
((today.month, today.day) < (birthday.month, birthday.day))
|
||||
today.year - birthday.year - ((today.month, today.day) < (birthday.month, birthday.day))
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -72,7 +72,6 @@ def pagination(paginator, page):
|
||||
# we muck around here to remove the pages not inmediately 'around' the current
|
||||
# one, otherwise we end up with a useless block with 300 pages.
|
||||
if paginator.num_pages > PAGINATION_MAX_TOTAL_PAGES:
|
||||
|
||||
start_page = page.number - PAGINATION_PAGES_AROUND_CURRENT
|
||||
for i in range(page.number - PAGINATION_PAGES_AROUND_CURRENT, page.number + 1):
|
||||
if i > 0:
|
||||
@@ -111,7 +110,7 @@ def render_muscles(muscles=None, muscles_sec=None):
|
||||
"""
|
||||
Renders the given muscles
|
||||
"""
|
||||
out = {"backgrounds": []}
|
||||
out = {'backgrounds': []}
|
||||
if not muscles and not muscles_sec:
|
||||
return out
|
||||
|
||||
@@ -124,13 +123,15 @@ def render_muscles(muscles=None, muscles_sec=None):
|
||||
out_secondary = muscles_sec if isinstance(muscles_sec, Iterable) else [muscles_sec]
|
||||
|
||||
if out_main:
|
||||
front_back = "front" if out_main[0].is_front else "back"
|
||||
front_back = 'front' if out_main[0].is_front else 'back'
|
||||
else:
|
||||
front_back = "front" if out_secondary[0].is_front else "back"
|
||||
front_back = 'front' if out_secondary[0].is_front else 'back'
|
||||
|
||||
out['backgrounds'] = [i.image_url_main for i in out_main] \
|
||||
+ [i.image_url_secondary for i in out_secondary] \
|
||||
+ [static(f"images/muscles/muscular_system_{front_back}.svg")]
|
||||
out['backgrounds'] = (
|
||||
[i.image_url_main for i in out_main]
|
||||
+ [i.image_url_secondary for i in out_secondary]
|
||||
+ [static(f'images/muscles/muscular_system_{front_back}.svg')]
|
||||
)
|
||||
|
||||
return out
|
||||
|
||||
@@ -203,12 +204,12 @@ def trans_weight_unit(unit, user=None):
|
||||
if unit == 'kg':
|
||||
return _('kg')
|
||||
if unit == 'g':
|
||||
return pgettext("weight unit, i.e. grams", "g")
|
||||
return pgettext('weight unit, i.e. grams', 'g')
|
||||
else:
|
||||
if unit == 'kg':
|
||||
return _('lb')
|
||||
if unit == 'g':
|
||||
return pgettext("weight unit, i.e. ounces", "oz")
|
||||
return pgettext('weight unit, i.e. ounces', 'oz')
|
||||
|
||||
|
||||
@register.filter
|
||||
|
||||
@@ -231,7 +231,7 @@ class ApiPostTestCase:
|
||||
response = self.client.post(self.url_detail, data=self.data)
|
||||
self.assertIn(
|
||||
response.status_code,
|
||||
(status.HTTP_405_METHOD_NOT_ALLOWED, status.HTTP_403_FORBIDDEN)
|
||||
(status.HTTP_405_METHOD_NOT_ALLOWED, status.HTTP_403_FORBIDDEN),
|
||||
)
|
||||
|
||||
# Authorized user (owner)
|
||||
@@ -239,7 +239,7 @@ class ApiPostTestCase:
|
||||
response = self.client.post(self.url_detail, data=self.data)
|
||||
self.assertIn(
|
||||
response.status_code,
|
||||
(status.HTTP_405_METHOD_NOT_ALLOWED, status.HTTP_403_FORBIDDEN)
|
||||
(status.HTTP_405_METHOD_NOT_ALLOWED, status.HTTP_403_FORBIDDEN),
|
||||
)
|
||||
|
||||
# Different logged in user
|
||||
@@ -247,7 +247,7 @@ class ApiPostTestCase:
|
||||
response = self.client.post(self.url_detail, data=self.data)
|
||||
self.assertIn(
|
||||
response.status_code,
|
||||
(status.HTTP_405_METHOD_NOT_ALLOWED, status.HTTP_403_FORBIDDEN)
|
||||
(status.HTTP_405_METHOD_NOT_ALLOWED, status.HTTP_403_FORBIDDEN),
|
||||
)
|
||||
|
||||
def test_post(self):
|
||||
@@ -279,7 +279,7 @@ class ApiPostTestCase:
|
||||
response = self.client.post(self.url, data=self.data)
|
||||
self.assertIn(
|
||||
response.status_code,
|
||||
(status.HTTP_405_METHOD_NOT_ALLOWED, status.HTTP_403_FORBIDDEN)
|
||||
(status.HTTP_405_METHOD_NOT_ALLOWED, status.HTTP_403_FORBIDDEN),
|
||||
)
|
||||
|
||||
# Logged in user
|
||||
@@ -287,7 +287,7 @@ class ApiPostTestCase:
|
||||
response = self.client.post(self.url, data=self.data)
|
||||
self.assertIn(
|
||||
response.status_code,
|
||||
(status.HTTP_405_METHOD_NOT_ALLOWED, status.HTTP_403_FORBIDDEN)
|
||||
(status.HTTP_405_METHOD_NOT_ALLOWED, status.HTTP_403_FORBIDDEN),
|
||||
)
|
||||
|
||||
# Different logged in user
|
||||
@@ -298,7 +298,7 @@ class ApiPostTestCase:
|
||||
else:
|
||||
self.assertIn(
|
||||
response.status_code,
|
||||
(status.HTTP_405_METHOD_NOT_ALLOWED, status.HTTP_403_FORBIDDEN)
|
||||
(status.HTTP_405_METHOD_NOT_ALLOWED, status.HTTP_403_FORBIDDEN),
|
||||
)
|
||||
|
||||
def test_post_special_endpoints(self):
|
||||
@@ -362,7 +362,7 @@ class ApiPatchTestCase:
|
||||
response = self.client.patch(self.url_detail, data=self.data)
|
||||
self.assertIn(
|
||||
response.status_code,
|
||||
(status.HTTP_405_METHOD_NOT_ALLOWED, status.HTTP_403_FORBIDDEN)
|
||||
(status.HTTP_405_METHOD_NOT_ALLOWED, status.HTTP_403_FORBIDDEN),
|
||||
)
|
||||
|
||||
# Authorized user (owner)
|
||||
@@ -370,7 +370,7 @@ class ApiPatchTestCase:
|
||||
response = self.client.patch(self.url_detail, data=self.data)
|
||||
self.assertIn(
|
||||
response.status_code,
|
||||
(status.HTTP_405_METHOD_NOT_ALLOWED, status.HTTP_403_FORBIDDEN)
|
||||
(status.HTTP_405_METHOD_NOT_ALLOWED, status.HTTP_403_FORBIDDEN),
|
||||
)
|
||||
|
||||
# Different logged in user
|
||||
@@ -381,7 +381,7 @@ class ApiPatchTestCase:
|
||||
else:
|
||||
self.assertIn(
|
||||
response.status_code,
|
||||
(status.HTTP_405_METHOD_NOT_ALLOWED, status.HTTP_403_FORBIDDEN)
|
||||
(status.HTTP_405_METHOD_NOT_ALLOWED, status.HTTP_403_FORBIDDEN),
|
||||
)
|
||||
|
||||
def test_patch(self):
|
||||
@@ -398,7 +398,7 @@ class ApiPatchTestCase:
|
||||
response = self.client.patch(self.url, data=self.data)
|
||||
self.assertIn(
|
||||
response.status_code,
|
||||
(status.HTTP_405_METHOD_NOT_ALLOWED, status.HTTP_403_FORBIDDEN)
|
||||
(status.HTTP_405_METHOD_NOT_ALLOWED, status.HTTP_403_FORBIDDEN),
|
||||
)
|
||||
|
||||
# Logged in user
|
||||
@@ -488,7 +488,7 @@ class ApiPutTestCase:
|
||||
response = self.client.put(self.url_detail, data=self.data)
|
||||
self.assertIn(
|
||||
response.status_code,
|
||||
(status.HTTP_405_METHOD_NOT_ALLOWED, status.HTTP_403_FORBIDDEN)
|
||||
(status.HTTP_405_METHOD_NOT_ALLOWED, status.HTTP_403_FORBIDDEN),
|
||||
)
|
||||
|
||||
# Authorized user (owner)
|
||||
@@ -496,7 +496,7 @@ class ApiPutTestCase:
|
||||
response = self.client.put(self.url_detail, data=self.data)
|
||||
self.assertIn(
|
||||
response.status_code,
|
||||
(status.HTTP_405_METHOD_NOT_ALLOWED, status.HTTP_403_FORBIDDEN)
|
||||
(status.HTTP_405_METHOD_NOT_ALLOWED, status.HTTP_403_FORBIDDEN),
|
||||
)
|
||||
|
||||
# Different logged in user
|
||||
@@ -507,7 +507,7 @@ class ApiPutTestCase:
|
||||
else:
|
||||
self.assertIn(
|
||||
response.status_code,
|
||||
(status.HTTP_405_METHOD_NOT_ALLOWED, status.HTTP_403_FORBIDDEN)
|
||||
(status.HTTP_405_METHOD_NOT_ALLOWED, status.HTTP_403_FORBIDDEN),
|
||||
)
|
||||
|
||||
def test_put(self):
|
||||
@@ -525,7 +525,7 @@ class ApiPutTestCase:
|
||||
response = self.client.put(self.url, data=self.data)
|
||||
self.assertIn(
|
||||
response.status_code,
|
||||
(status.HTTP_405_METHOD_NOT_ALLOWED, status.HTTP_403_FORBIDDEN)
|
||||
(status.HTTP_405_METHOD_NOT_ALLOWED, status.HTTP_403_FORBIDDEN),
|
||||
)
|
||||
|
||||
# Authorized user (owner)
|
||||
@@ -601,7 +601,7 @@ class ApiDeleteTestCase:
|
||||
response = self.client.delete(self.url_detail)
|
||||
self.assertIn(
|
||||
response.status_code,
|
||||
(status.HTTP_405_METHOD_NOT_ALLOWED, status.HTTP_403_FORBIDDEN)
|
||||
(status.HTTP_405_METHOD_NOT_ALLOWED, status.HTTP_403_FORBIDDEN),
|
||||
)
|
||||
|
||||
# Logged in user
|
||||
@@ -609,7 +609,7 @@ class ApiDeleteTestCase:
|
||||
response = self.client.delete(self.url_detail)
|
||||
self.assertIn(
|
||||
response.status_code,
|
||||
(status.HTTP_405_METHOD_NOT_ALLOWED, status.HTTP_403_FORBIDDEN)
|
||||
(status.HTTP_405_METHOD_NOT_ALLOWED, status.HTTP_403_FORBIDDEN),
|
||||
)
|
||||
|
||||
# Different logged in user
|
||||
@@ -617,7 +617,7 @@ class ApiDeleteTestCase:
|
||||
response = self.client.delete(self.url_detail)
|
||||
self.assertIn(
|
||||
response.status_code,
|
||||
(status.HTTP_405_METHOD_NOT_ALLOWED, status.HTTP_403_FORBIDDEN)
|
||||
(status.HTTP_405_METHOD_NOT_ALLOWED, status.HTTP_403_FORBIDDEN),
|
||||
)
|
||||
|
||||
def test_delete(self):
|
||||
@@ -643,7 +643,7 @@ class ApiDeleteTestCase:
|
||||
response = self.client.delete(self.url)
|
||||
self.assertIn(
|
||||
response.status_code,
|
||||
(status.HTTP_405_METHOD_NOT_ALLOWED, status.HTTP_403_FORBIDDEN)
|
||||
(status.HTTP_405_METHOD_NOT_ALLOWED, status.HTTP_403_FORBIDDEN),
|
||||
)
|
||||
|
||||
# Authorized user (owner)
|
||||
@@ -651,7 +651,7 @@ class ApiDeleteTestCase:
|
||||
response = self.client.delete(self.url)
|
||||
self.assertIn(
|
||||
response.status_code,
|
||||
(status.HTTP_405_METHOD_NOT_ALLOWED, status.HTTP_403_FORBIDDEN)
|
||||
(status.HTTP_405_METHOD_NOT_ALLOWED, status.HTTP_403_FORBIDDEN),
|
||||
)
|
||||
|
||||
# Different logged in user
|
||||
@@ -659,7 +659,7 @@ class ApiDeleteTestCase:
|
||||
response = self.client.delete(self.url)
|
||||
self.assertIn(
|
||||
response.status_code,
|
||||
(status.HTTP_405_METHOD_NOT_ALLOWED, status.HTTP_403_FORBIDDEN)
|
||||
(status.HTTP_405_METHOD_NOT_ALLOWED, status.HTTP_403_FORBIDDEN),
|
||||
)
|
||||
|
||||
def test_delete_special_endpoints(self):
|
||||
@@ -700,6 +700,7 @@ class ApiBaseResourceTestCase(
|
||||
|
||||
All logic happens in the Api*TestCase classes
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@@ -185,15 +185,15 @@ class BaseTestCase:
|
||||
|
||||
shutil.copy(
|
||||
'wger/exercises/tests/protestschwein.jpg',
|
||||
self.media_root + '/exercise-images/1/protestschwein.jpg'
|
||||
self.media_root + '/exercise-images/1/protestschwein.jpg',
|
||||
)
|
||||
shutil.copy(
|
||||
'wger/exercises/tests/wildschwein.jpg',
|
||||
self.media_root + '/exercise-images/1/wildschwein.jpg'
|
||||
self.media_root + '/exercise-images/1/wildschwein.jpg',
|
||||
)
|
||||
shutil.copy(
|
||||
'wger/exercises/tests/wildschwein.jpg',
|
||||
self.media_root + '/exercise-images/2/wildschwein.jpg'
|
||||
self.media_root + '/exercise-images/2/wildschwein.jpg',
|
||||
)
|
||||
|
||||
|
||||
@@ -253,7 +253,6 @@ class WgerTestCase(BaseTestCase, TestCase):
|
||||
|
||||
# Uploaded image or file, compare the filename
|
||||
elif current_field_class in ('ImageFieldFile', 'FieldFile'):
|
||||
|
||||
# We can only compare the extensions, since the names can be changed
|
||||
# Ideally we would check that the byte length is the same
|
||||
self.assertEqual(pathlib.Path(field.name).suffix, pathlib.Path(value.name).suffix)
|
||||
@@ -564,7 +563,6 @@ class WgerAccessTestCase(WgerTestCase):
|
||||
anonymous_fail = True
|
||||
|
||||
def access(self, fail=True):
|
||||
|
||||
# Only perform the checks on derived classes
|
||||
if self.__class__.__name__ == 'WgerAccessTestCase':
|
||||
return
|
||||
|
||||
@@ -32,7 +32,6 @@ class ChangePasswordTestCase(WgerTestCase):
|
||||
"""
|
||||
|
||||
def change_password(self, fail=True):
|
||||
|
||||
# Fetch the change passwort page
|
||||
response = self.client.get(reverse('core:user:change-password'))
|
||||
|
||||
|
||||
@@ -28,13 +28,14 @@ class DaysOfWeekRepresentationTestCase(WgerTestCase):
|
||||
"""
|
||||
Test that the representation of an object is correct
|
||||
"""
|
||||
self.assertEqual(f"{DaysOfWeek.objects.get(pk=1)}", 'Monday')
|
||||
self.assertEqual(f'{DaysOfWeek.objects.get(pk=1)}', 'Monday')
|
||||
|
||||
|
||||
class DaysOfWeekApiTestCase(api_base_test.ApiBaseResourceTestCase):
|
||||
"""
|
||||
Tests the days of week resource
|
||||
"""
|
||||
|
||||
pk = 1
|
||||
resource = DaysOfWeek
|
||||
private_resource = False
|
||||
|
||||
@@ -86,8 +86,9 @@ class DeleteUserByAdminTestCase(WgerTestCase):
|
||||
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}'
|
||||
response.status_code,
|
||||
(302, 403),
|
||||
f'Unexpected status code for user {self.current_user}',
|
||||
)
|
||||
else:
|
||||
self.assertEqual(
|
||||
|
||||
@@ -55,10 +55,11 @@ class FeedbackTestCase(WgerTestCase):
|
||||
|
||||
# Correctly filled in reCaptcha
|
||||
response = self.client.post(
|
||||
reverse('core:feedback'), {
|
||||
reverse('core:feedback'),
|
||||
{
|
||||
'comment': 'A very long and interesting comment',
|
||||
'g-recaptcha-response': 'PASSED'
|
||||
}
|
||||
'g-recaptcha-response': 'PASSED',
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
@@ -82,7 +83,7 @@ class FeedbackTestCase(WgerTestCase):
|
||||
self.user_login('test')
|
||||
self.send_feedback()
|
||||
|
||||
@skip("Failing due to recaptcha issues")
|
||||
@skip('Failing due to recaptcha issues')
|
||||
def test_send_feedback_logged_out(self):
|
||||
"""
|
||||
Tests the feedback form as a logged out user
|
||||
|
||||
@@ -22,7 +22,6 @@ from wger.gym.models import Gym
|
||||
|
||||
|
||||
class UserGeneratorTestCase(WgerTestCase):
|
||||
|
||||
def test_generator(self):
|
||||
# Arrange
|
||||
User.objects.all().delete()
|
||||
|
||||
@@ -60,11 +60,14 @@ class DashboardTestCase(WgerTestCase):
|
||||
#
|
||||
# 2. 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)
|
||||
|
||||
@@ -39,7 +39,7 @@ class LanguageRepresentationTestCase(WgerTestCase):
|
||||
"""
|
||||
Test that the representation of an object is correct
|
||||
"""
|
||||
self.assertEqual(f"{Language.objects.get(pk=1)}", 'Deutsch (de)')
|
||||
self.assertEqual(f'{Language.objects.get(pk=1)}', 'Deutsch (de)')
|
||||
|
||||
|
||||
class LanguageOverviewTest(WgerAccessTestCase):
|
||||
@@ -95,6 +95,7 @@ class LanguageApiTestCase(api_base_test.ApiBaseResourceTestCase):
|
||||
"""
|
||||
Tests the language overview resource
|
||||
"""
|
||||
|
||||
pk = 1
|
||||
resource = Language
|
||||
private_resource = False
|
||||
|
||||
@@ -35,7 +35,7 @@ class LicenseRepresentationTestCase(WgerTestCase):
|
||||
Test that the representation of an object is correct
|
||||
"""
|
||||
self.assertEqual(
|
||||
f"{License.objects.get(pk=1)}",
|
||||
f'{License.objects.get(pk=1)}',
|
||||
'A cool and free license - Germany (ACAFL - DE)',
|
||||
)
|
||||
|
||||
@@ -83,6 +83,7 @@ class LicenseApiTestCase(api_base_test.ApiBaseResourceTestCase):
|
||||
"""
|
||||
Tests the license resource
|
||||
"""
|
||||
|
||||
pk = 1
|
||||
resource = License
|
||||
private_resource = False
|
||||
|
||||
@@ -22,7 +22,6 @@ from wger.core.tests.base_testcase import BaseTestCase
|
||||
|
||||
|
||||
class CheckPermissionApiTestCase(BaseTestCase, ApiBaseTestCase):
|
||||
|
||||
url = '/api/v2/check-permission/'
|
||||
error_message = "Please pass a permission name in the 'permission' parameter"
|
||||
|
||||
|
||||
@@ -51,7 +51,8 @@ class PreferencesTestCase(WgerTestCase):
|
||||
|
||||
# Change some preferences
|
||||
response = self.client.post(
|
||||
reverse('core:user:preferences'), {
|
||||
reverse('core:user:preferences'),
|
||||
{
|
||||
'show_comments': True,
|
||||
'show_english_ingredients': True,
|
||||
'email': 'my-new-email@example.com',
|
||||
@@ -62,7 +63,7 @@ class PreferencesTestCase(WgerTestCase):
|
||||
'num_days_weight_reminder': 10,
|
||||
'weight_unit': 'kg',
|
||||
'birthdate': '02/25/1987',
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
@@ -76,7 +77,8 @@ class PreferencesTestCase(WgerTestCase):
|
||||
|
||||
# Change some preferences
|
||||
response = self.client.post(
|
||||
reverse('core:user:preferences'), {
|
||||
reverse('core:user:preferences'),
|
||||
{
|
||||
'show_comments': False,
|
||||
'show_english_ingredients': True,
|
||||
'email': '',
|
||||
@@ -87,7 +89,7 @@ class PreferencesTestCase(WgerTestCase):
|
||||
'num_days_weight_reminder': 10,
|
||||
'weight_unit': 'lb',
|
||||
'birthdate': '02/25/1987',
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
@@ -105,23 +107,25 @@ class PreferencesTestCase(WgerTestCase):
|
||||
# Member2 has a contract
|
||||
user = User.objects.get(username='member2')
|
||||
self.assertEqual(
|
||||
user.userprofile.address, {
|
||||
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, {
|
||||
user.userprofile.address,
|
||||
{
|
||||
'phone': '',
|
||||
'zip_code': '',
|
||||
'street': '',
|
||||
'city': '',
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -246,8 +250,8 @@ class PreferencesCalculationsTestCase(WgerTestCase):
|
||||
bmi = user.userprofile.calculate_bmi()
|
||||
self.assertEqual(
|
||||
bmi,
|
||||
user.userprofile.weight.quantize(TWOPLACES) /
|
||||
decimal.Decimal(1.80 * 1.80).quantize(TWOPLACES)
|
||||
user.userprofile.weight.quantize(TWOPLACES)
|
||||
/ decimal.Decimal(1.80 * 1.80).quantize(TWOPLACES),
|
||||
)
|
||||
|
||||
def test_basal_metabolic_rate(self):
|
||||
@@ -263,7 +267,7 @@ class PreferencesCalculationsTestCase(WgerTestCase):
|
||||
self.assertEqual(bmr, 1860)
|
||||
|
||||
# Female
|
||||
user.userprofile.gender = "2"
|
||||
user.userprofile.gender = '2'
|
||||
bmr = user.userprofile.calculate_basal_metabolic_rate()
|
||||
self.assertEqual(bmr, 1694)
|
||||
|
||||
@@ -281,29 +285,25 @@ class PreferencesCalculationsTestCase(WgerTestCase):
|
||||
user = User.objects.get(pk=2)
|
||||
|
||||
self.assertEqual(
|
||||
user.userprofile.calculate_activities(),
|
||||
decimal.Decimal(1.57).quantize(TWOPLACES)
|
||||
user.userprofile.calculate_activities(), decimal.Decimal(1.57).quantize(TWOPLACES)
|
||||
)
|
||||
|
||||
# Gender has no influence
|
||||
user.userprofile.gender = "2"
|
||||
user.userprofile.gender = '2'
|
||||
self.assertEqual(
|
||||
user.userprofile.calculate_activities(),
|
||||
decimal.Decimal(1.57).quantize(TWOPLACES)
|
||||
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)
|
||||
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)
|
||||
user.userprofile.calculate_activities(), decimal.Decimal(1.52).quantize(TWOPLACES)
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -134,23 +134,23 @@ class RegistrationTestCase(WgerTestCase):
|
||||
self.user_logout()
|
||||
|
||||
# First password is missing
|
||||
registration_data['password1'] = ""
|
||||
registration_data['password1'] = ''
|
||||
response = self.client.post(reverse('core:user:registration'), registration_data)
|
||||
self.assertFalse(response.context['form'].is_valid())
|
||||
self.user_logout()
|
||||
|
||||
# Second password is missing
|
||||
registration_data['password2'] = ""
|
||||
registration_data['password2'] = ''
|
||||
response = self.client.post(reverse('core:user:registration'), registration_data)
|
||||
self.assertFalse(response.context['form'].is_valid())
|
||||
self.user_logout()
|
||||
|
||||
# Username is too long
|
||||
long_user = (
|
||||
"my_username_is_"
|
||||
"wayyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
|
||||
"_toooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo"
|
||||
"_loooooooooooooooooooooooooooooooooooooooooooooooooooooooooong"
|
||||
'my_username_is_'
|
||||
'wayyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy'
|
||||
'_toooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo'
|
||||
'_loooooooooooooooooooooooooooooooooooooooooooooooooooooooooong'
|
||||
)
|
||||
registration_data['username'] = long_user
|
||||
response = self.client.post(reverse('core:user:registration'), registration_data)
|
||||
@@ -158,13 +158,13 @@ class RegistrationTestCase(WgerTestCase):
|
||||
self.user_logout()
|
||||
|
||||
# Username contains invalid symbol
|
||||
registration_data['username'] = "username!"
|
||||
registration_data['username'] = 'username!'
|
||||
response = self.client.post(reverse('core:user:registration'), registration_data)
|
||||
self.assertFalse(response.context['form'].is_valid())
|
||||
self.user_logout()
|
||||
|
||||
# Username is missing
|
||||
registration_data['username'] = ""
|
||||
registration_data['username'] = ''
|
||||
response = self.client.post(reverse('core:user:registration'), registration_data)
|
||||
self.assertFalse(response.context['form'].is_valid())
|
||||
self.user_logout()
|
||||
|
||||
@@ -34,7 +34,7 @@ class RepresentationTestCase(WgerTestCase):
|
||||
"""
|
||||
Test that the representation of an object is correct
|
||||
"""
|
||||
self.assertEqual(f"{RepetitionUnit.objects.get(pk=1)}", 'Repetitions')
|
||||
self.assertEqual(f'{RepetitionUnit.objects.get(pk=1)}', 'Repetitions')
|
||||
|
||||
|
||||
class OverviewTest(WgerAccessTestCase):
|
||||
@@ -54,7 +54,7 @@ class AddTestCase(WgerAddTestCase):
|
||||
object_class = RepetitionUnit
|
||||
url = 'core:repetition-unit:add'
|
||||
data = {'name': 'Furlongs'}
|
||||
user_success = 'admin',
|
||||
user_success = ('admin',)
|
||||
user_fail = (
|
||||
'general_manager1',
|
||||
'general_manager2',
|
||||
@@ -75,7 +75,7 @@ class DeleteTestCase(WgerDeleteTestCase):
|
||||
pk = 1
|
||||
object_class = RepetitionUnit
|
||||
url = 'core:repetition-unit:delete'
|
||||
user_success = 'admin',
|
||||
user_success = ('admin',)
|
||||
user_fail = (
|
||||
'general_manager1',
|
||||
'general_manager2',
|
||||
@@ -97,7 +97,7 @@ class EditTestCase(WgerEditTestCase):
|
||||
object_class = RepetitionUnit
|
||||
url = 'core:repetition-unit:edit'
|
||||
data = {'name': 'Furlongs'}
|
||||
user_success = 'admin',
|
||||
user_success = ('admin',)
|
||||
user_fail = (
|
||||
'general_manager1',
|
||||
'general_manager2',
|
||||
@@ -114,6 +114,7 @@ class ApiTestCase(api_base_test.ApiBaseResourceTestCase):
|
||||
"""
|
||||
Tests the unit resource
|
||||
"""
|
||||
|
||||
pk = 1
|
||||
resource = RepetitionUnit
|
||||
private_resource = False
|
||||
|
||||
@@ -26,7 +26,6 @@ class RobotsTxtTestCase(WgerTestCase):
|
||||
"""
|
||||
|
||||
def test_robots(self):
|
||||
|
||||
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))
|
||||
|
||||
@@ -25,13 +25,11 @@ class SitemapTestCase(WgerTestCase):
|
||||
"""
|
||||
|
||||
def test_sitemap_index(self):
|
||||
|
||||
response = self.client.get(reverse('sitemap'))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(len(response.context['sitemaps']), 2)
|
||||
|
||||
def test_sitemap_exercises(self):
|
||||
|
||||
response = self.client.get(
|
||||
reverse('django.contrib.sitemaps.views.sitemap', kwargs={'section': 'exercises'})
|
||||
)
|
||||
@@ -39,7 +37,6 @@ class SitemapTestCase(WgerTestCase):
|
||||
self.assertEqual(len(response.context['urlset']), 11)
|
||||
|
||||
def test_sitemap_ingredients(self):
|
||||
|
||||
response = self.client.get(
|
||||
reverse('django.contrib.sitemaps.views.sitemap', kwargs={'section': 'nutrition'})
|
||||
)
|
||||
|
||||
@@ -37,7 +37,6 @@ def _build_mock_user(gym_name, is_trainer=False):
|
||||
|
||||
@mock.patch('wger.core.views.user.django_login')
|
||||
class TrainerLoginTestCase(WgerTestCase):
|
||||
|
||||
def test_trainer_is_allowed_to_login_to_non_trainer_in_same_gym(self, _):
|
||||
request_user = _build_mock_user('same-gym', is_trainer=True)
|
||||
request = _build_mock_request(request_user)
|
||||
@@ -92,10 +91,7 @@ class UserApiLoginApiTestCase(BaseTestCase, ApiBaseTestCase):
|
||||
def test_login_username_success(self):
|
||||
response = self.client.post(
|
||||
self.url,
|
||||
{
|
||||
'username': 'admin',
|
||||
'password': 'adminadmin'
|
||||
},
|
||||
{'username': 'admin', 'password': 'adminadmin'},
|
||||
)
|
||||
result = response.data
|
||||
|
||||
@@ -109,16 +105,13 @@ class UserApiLoginApiTestCase(BaseTestCase, ApiBaseTestCase):
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertEqual(
|
||||
result['non_field_errors'],
|
||||
[ErrorDetail(string='Username or password unknown', code='invalid')]
|
||||
[ErrorDetail(string='Username or password unknown', code='invalid')],
|
||||
)
|
||||
|
||||
def test_login_email_success(self):
|
||||
response = self.client.post(
|
||||
self.url,
|
||||
{
|
||||
'email': 'admin@example.com',
|
||||
'password': 'adminadmin'
|
||||
},
|
||||
{'email': 'admin@example.com', 'password': 'adminadmin'},
|
||||
)
|
||||
result = response.data
|
||||
|
||||
@@ -127,17 +120,14 @@ class UserApiLoginApiTestCase(BaseTestCase, ApiBaseTestCase):
|
||||
|
||||
def test_login_email_fail(self):
|
||||
response = self.client.post(
|
||||
self.url, {
|
||||
'email': 'admin@example.com',
|
||||
'password': 'adminadmin123'
|
||||
}
|
||||
self.url, {'email': 'admin@example.com', 'password': 'adminadmin123'}
|
||||
)
|
||||
result = response.data
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertEqual(
|
||||
result['non_field_errors'],
|
||||
[ErrorDetail(string='Username or password unknown', code='invalid')]
|
||||
[ErrorDetail(string='Username or password unknown', code='invalid')],
|
||||
)
|
||||
|
||||
def test_no_parameters(self):
|
||||
@@ -147,5 +137,5 @@ class UserApiLoginApiTestCase(BaseTestCase, ApiBaseTestCase):
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertEqual(
|
||||
result['non_field_errors'],
|
||||
[ErrorDetail(string='Please provide an "email" or a "username"', code='invalid')]
|
||||
[ErrorDetail(string='Please provide an "email" or a "username"', code='invalid')],
|
||||
)
|
||||
|
||||
@@ -19,7 +19,6 @@ from wger.core.tests.base_testcase import WgerTestCase
|
||||
|
||||
|
||||
class CreateUserCommand(WgerTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(CreateUserCommand, self).setUp()
|
||||
self.out = StringIO()
|
||||
@@ -68,20 +67,17 @@ class CreateUserCommand(WgerTestCase):
|
||||
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}'
|
||||
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)
|
||||
|
||||
new_user = User.objects.get(username='restapi')
|
||||
token = Token.objects.get(user=new_user)
|
||||
self.assertEqual(response.data["message"], "api user successfully registered")
|
||||
self.assertEqual(response.data["token"], token.key)
|
||||
self.assertEqual(response.data['message'], 'api user successfully registered')
|
||||
self.assertEqual(response.data['token'], token.key)
|
||||
self.assertEqual(count_after, count_before + 1)
|
||||
|
||||
def test_post_valid_api_user_creation_no_email(self):
|
||||
@@ -93,19 +89,17 @@ class CreateUserCommand(WgerTestCase):
|
||||
count_before = User.objects.count()
|
||||
|
||||
response = self.client.post(
|
||||
reverse('api_register'), {
|
||||
'username': 'restapi',
|
||||
'password': 'AekaiLe0ga'
|
||||
},
|
||||
Authorization=f'Token {token.key}'
|
||||
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)
|
||||
|
||||
new_user = User.objects.get(username='restapi')
|
||||
token = Token.objects.get(user=new_user)
|
||||
self.assertEqual(response.data["message"], "api user successfully registered")
|
||||
self.assertEqual(response.data["token"], token.key)
|
||||
self.assertEqual(response.data['message'], 'api user successfully registered')
|
||||
self.assertEqual(response.data['token'], token.key)
|
||||
self.assertEqual(count_after, count_before + 1)
|
||||
|
||||
def test_post_not_allowed_api_user_creation(self):
|
||||
@@ -115,11 +109,8 @@ class CreateUserCommand(WgerTestCase):
|
||||
count_before = User.objects.count()
|
||||
|
||||
response = self.client.post(
|
||||
reverse('api_register'), {
|
||||
'username': 'restapi',
|
||||
'email': 'abc@cde.fg',
|
||||
'password': 'AekaiLe0ga'
|
||||
}
|
||||
reverse('api_register'),
|
||||
{'username': 'restapi', 'email': 'abc@cde.fg', 'password': 'AekaiLe0ga'},
|
||||
)
|
||||
count_after = User.objects.count()
|
||||
|
||||
@@ -149,12 +140,9 @@ class CreateUserCommand(WgerTestCase):
|
||||
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}'
|
||||
reverse('api_register'),
|
||||
{'username': 'restapi', 'email': 'example.com', 'password': 'AekaiLe0ga'},
|
||||
Authorization=f'Token {token.key}',
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST)
|
||||
@@ -167,12 +155,9 @@ class CreateUserCommand(WgerTestCase):
|
||||
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}'
|
||||
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)
|
||||
|
||||
@@ -34,7 +34,7 @@ class RepresentationTestCase(WgerTestCase):
|
||||
"""
|
||||
Test that the representation of an object is correct
|
||||
"""
|
||||
self.assertEqual(f"{WeightUnit.objects.get(pk=1)}", 'kg')
|
||||
self.assertEqual(f'{WeightUnit.objects.get(pk=1)}', 'kg')
|
||||
|
||||
|
||||
class OverviewTest(WgerAccessTestCase):
|
||||
@@ -54,7 +54,7 @@ class AddTestCase(WgerAddTestCase):
|
||||
object_class = WeightUnit
|
||||
url = 'core:weight-unit:add'
|
||||
data = {'name': 'Furlongs'}
|
||||
user_success = 'admin',
|
||||
user_success = ('admin',)
|
||||
user_fail = (
|
||||
'general_manager1',
|
||||
'general_manager2',
|
||||
@@ -75,7 +75,7 @@ class DeleteTestCase(WgerDeleteTestCase):
|
||||
pk = 1
|
||||
object_class = WeightUnit
|
||||
url = 'core:weight-unit:delete'
|
||||
user_success = 'admin',
|
||||
user_success = ('admin',)
|
||||
user_fail = (
|
||||
'general_manager1',
|
||||
'general_manager2',
|
||||
@@ -97,7 +97,7 @@ class EditTestCase(WgerEditTestCase):
|
||||
object_class = WeightUnit
|
||||
url = 'core:weight-unit:edit'
|
||||
data = {'name': 'Furlongs'}
|
||||
user_success = 'admin',
|
||||
user_success = ('admin',)
|
||||
user_fail = (
|
||||
'general_manager1',
|
||||
'general_manager2',
|
||||
@@ -114,6 +114,7 @@ class ApiTestCase(api_base_test.ApiBaseResourceTestCase):
|
||||
"""
|
||||
Tests the unit resource
|
||||
"""
|
||||
|
||||
pk = 1
|
||||
resource = WeightUnit
|
||||
private_resource = False
|
||||
|
||||
@@ -69,7 +69,7 @@ patterns_user = [
|
||||
path(
|
||||
'login',
|
||||
views.LoginView.as_view(template_name='user/login.html', authentication_form=UserLoginForm),
|
||||
name='login'
|
||||
name='login',
|
||||
),
|
||||
path('logout', user.logout, name='logout'),
|
||||
path('delete', user.delete, name='delete'),
|
||||
@@ -85,7 +85,6 @@ patterns_user = [
|
||||
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(
|
||||
@@ -191,17 +190,14 @@ patterns_weight_units = [
|
||||
# Actual patterns
|
||||
#
|
||||
urlpatterns = [
|
||||
|
||||
# The landing page
|
||||
path('', misc.index, name='index'),
|
||||
|
||||
# The dashboard
|
||||
path('dashboard', misc.dashboard, name='dashboard'),
|
||||
|
||||
# Others
|
||||
path(
|
||||
'imprint',
|
||||
TemplateView.as_view(template_name="misc/about.html"),
|
||||
TemplateView.as_view(template_name='misc/about.html'),
|
||||
name='imprint',
|
||||
),
|
||||
path(
|
||||
@@ -209,12 +205,12 @@ urlpatterns = [
|
||||
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('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")
|
||||
include((patterns_repetition_units, 'repetition-unit'), namespace='repetition-unit'),
|
||||
),
|
||||
path('weight-unit/', include((patterns_weight_units, 'weight-unit'), namespace="weight-unit")),
|
||||
path('weight-unit/', include((patterns_weight_units, 'weight-unit'), namespace='weight-unit')),
|
||||
]
|
||||
|
||||
@@ -50,6 +50,7 @@ class LanguageListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
||||
"""
|
||||
Show an overview of all languages
|
||||
"""
|
||||
|
||||
model = Language
|
||||
template_name = 'language/overview.html'
|
||||
context_object_name = 'language_list'
|
||||
|
||||
@@ -49,6 +49,7 @@ class LicenseListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
||||
"""
|
||||
Overview of all available licenses
|
||||
"""
|
||||
|
||||
model = License
|
||||
permission_required = 'core.add_license'
|
||||
template_name = 'license/list.html'
|
||||
|
||||
@@ -75,11 +75,8 @@ def demo_entries(request):
|
||||
return HttpResponseRedirect(reverse('software:features'))
|
||||
|
||||
if (
|
||||
(
|
||||
(not request.user.is_authenticated or request.user.userprofile.is_temporary)
|
||||
and not request.session['has_demo_data']
|
||||
)
|
||||
):
|
||||
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:
|
||||
@@ -96,7 +93,7 @@ def demo_entries(request):
|
||||
'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'))
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ class ListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
||||
"""
|
||||
Overview of all available setting units
|
||||
"""
|
||||
|
||||
model = RepetitionUnit
|
||||
permission_required = 'core.add_repetitionunit'
|
||||
template_name = 'repetition_unit/list.html'
|
||||
|
||||
@@ -113,7 +113,7 @@ def login(request):
|
||||
Small wrapper around the django login view
|
||||
"""
|
||||
|
||||
next_url = "?next=" + request.GET.get('next') if request.GET.get('next') else ''
|
||||
next_url = '?next=' + request.GET.get('next') if request.GET.get('next') else ''
|
||||
|
||||
form = UserLoginForm
|
||||
form.helper.form_action = reverse('core:user:login') + next_url
|
||||
@@ -135,12 +135,13 @@ def delete(request, user_pk=None):
|
||||
|
||||
# Forbidden if the user has not enough rights, doesn't belong to the
|
||||
# gym or is an admin as well. General admins can delete all users.
|
||||
if not request.user.has_perm('gym.manage_gyms') \
|
||||
and (not request.user.has_perm('gym.manage_gym')
|
||||
or request.user.userprofile.gym_id != user.userprofile.gym_id
|
||||
or user.has_perm('gym.manage_gym')
|
||||
or user.has_perm('gym.gym_trainer')
|
||||
or user.has_perm('gym.manage_gyms')):
|
||||
if not request.user.has_perm('gym.manage_gyms') and (
|
||||
not request.user.has_perm('gym.manage_gym')
|
||||
or request.user.userprofile.gym_id != user.userprofile.gym_id
|
||||
or user.has_perm('gym.manage_gym')
|
||||
or user.has_perm('gym.gym_trainer')
|
||||
or user.has_perm('gym.manage_gyms')
|
||||
):
|
||||
return HttpResponseForbidden()
|
||||
else:
|
||||
user = request.user
|
||||
@@ -150,11 +151,9 @@ def delete(request, user_pk=None):
|
||||
if request.method == 'POST':
|
||||
form = PasswordConfirmationForm(data=request.POST, user=request.user)
|
||||
if form.is_valid():
|
||||
|
||||
user.delete()
|
||||
messages.success(
|
||||
request,
|
||||
_('Account "{0}" was successfully deleted').format(user.username)
|
||||
request, _('Account "{0}" was successfully deleted').format(user.username)
|
||||
)
|
||||
|
||||
if not user_pk:
|
||||
@@ -178,15 +177,15 @@ def trainer_login(request, user_pk):
|
||||
orig_user_pk = request.user.pk
|
||||
|
||||
# No changing if identity is not set
|
||||
if not request.user.has_perm('gym.gym_trainer') \
|
||||
and not request.session.get('trainer.identity'):
|
||||
if not request.user.has_perm('gym.gym_trainer') and not request.session.get('trainer.identity'):
|
||||
return HttpResponseForbidden()
|
||||
|
||||
# Changing between trainers or managers is not allowed
|
||||
if request.user.has_perm('gym.gym_trainer') \
|
||||
and (user.has_perm('gym.gym_trainer')
|
||||
or user.has_perm('gym.manage_gym')
|
||||
or user.has_perm('gym.manage_gyms')):
|
||||
if request.user.has_perm('gym.gym_trainer') and (
|
||||
user.has_perm('gym.gym_trainer')
|
||||
or user.has_perm('gym.manage_gym')
|
||||
or user.has_perm('gym.manage_gyms')
|
||||
):
|
||||
return HttpResponseForbidden()
|
||||
|
||||
# Changing is only allowed between the same gym
|
||||
@@ -201,7 +200,8 @@ def trainer_login(request, 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')
|
||||
user.has_perm('gym.gym_trainer')
|
||||
or user.has_perm('gym.manage_gym')
|
||||
or user.has_perm('gym.manage_gyms')
|
||||
):
|
||||
own = True
|
||||
@@ -209,7 +209,7 @@ def trainer_login(request, user_pk):
|
||||
# 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:
|
||||
@@ -248,8 +248,9 @@ def registration(request):
|
||||
template_data.update(csrf(request))
|
||||
|
||||
# Don't show captcha if the global parameter is false
|
||||
FormClass = RegistrationForm if settings.WGER_SETTINGS['USE_RECAPTCHA'] \
|
||||
else RegistrationFormNoCaptcha
|
||||
FormClass = (
|
||||
RegistrationForm if settings.WGER_SETTINGS['USE_RECAPTCHA'] else RegistrationFormNoCaptcha
|
||||
)
|
||||
|
||||
# Redirect regular users, in case they reached the registration page
|
||||
if request.user.is_authenticated and not request.user.userprofile.is_temporary:
|
||||
@@ -313,7 +314,6 @@ def preferences(request):
|
||||
|
||||
# Process the preferences form
|
||||
if request.method == 'POST':
|
||||
|
||||
form = UserPreferencesForm(data=request.POST, instance=request.user.userprofile)
|
||||
form.user = request.user
|
||||
|
||||
@@ -325,7 +325,7 @@ def preferences(request):
|
||||
data = {
|
||||
'first_name': request.user.first_name,
|
||||
'last_name': request.user.last_name,
|
||||
'email': request.user.email
|
||||
'email': request.user.email,
|
||||
}
|
||||
|
||||
form = UserPreferencesForm(initial=data, instance=request.user.userprofile)
|
||||
@@ -336,7 +336,6 @@ def preferences(request):
|
||||
email_form = UserPersonalInformationForm(data=request.POST, instance=request.user)
|
||||
|
||||
if email_form.is_valid() and redirect:
|
||||
|
||||
# If the user changes the email, it is no longer verified
|
||||
if user_email != email_form.instance.email:
|
||||
logger.debug('resetting verified flag')
|
||||
@@ -366,6 +365,7 @@ class UserDeactivateView(
|
||||
"""
|
||||
Deactivates a user
|
||||
"""
|
||||
|
||||
permanent = False
|
||||
model = User
|
||||
permission_required = ('gym.manage_gym', 'gym.manage_gyms', 'gym.gym_trainer')
|
||||
@@ -379,8 +379,9 @@ class UserDeactivateView(
|
||||
if not request.user.is_authenticated:
|
||||
return HttpResponseForbidden()
|
||||
|
||||
if (request.user.has_perm('gym.manage_gym') or request.user.has_perm('gym.gym_trainer')) \
|
||||
and edit_user.userprofile.gym_id != request.user.userprofile.gym_id:
|
||||
if (
|
||||
request.user.has_perm('gym.manage_gym') or request.user.has_perm('gym.gym_trainer')
|
||||
) and edit_user.userprofile.gym_id != request.user.userprofile.gym_id:
|
||||
return HttpResponseForbidden()
|
||||
|
||||
return super(UserDeactivateView, self).dispatch(request, *args, **kwargs)
|
||||
@@ -401,6 +402,7 @@ class UserActivateView(
|
||||
"""
|
||||
Activates a previously deactivated user
|
||||
"""
|
||||
|
||||
permanent = False
|
||||
model = User
|
||||
permission_required = ('gym.manage_gym', 'gym.manage_gyms', 'gym.gym_trainer')
|
||||
@@ -414,8 +416,9 @@ class UserActivateView(
|
||||
if not request.user.is_authenticated:
|
||||
return HttpResponseForbidden()
|
||||
|
||||
if (request.user.has_perm('gym.manage_gym') or request.user.has_perm('gym.gym_trainer')) \
|
||||
and edit_user.userprofile.gym_id != request.user.userprofile.gym_id:
|
||||
if (
|
||||
request.user.has_perm('gym.manage_gym') or request.user.has_perm('gym.gym_trainer')
|
||||
) and edit_user.userprofile.gym_id != request.user.userprofile.gym_id:
|
||||
return HttpResponseForbidden()
|
||||
|
||||
return super(UserActivateView, self).dispatch(request, *args, **kwargs)
|
||||
@@ -454,9 +457,11 @@ class UserEditView(
|
||||
if not user.is_authenticated:
|
||||
return HttpResponseForbidden()
|
||||
|
||||
if user.has_perm('gym.manage_gym') \
|
||||
and not user.has_perm('gym.manage_gyms') \
|
||||
and user.userprofile.gym != self.get_object().userprofile.gym:
|
||||
if (
|
||||
user.has_perm('gym.manage_gym')
|
||||
and not user.has_perm('gym.manage_gyms')
|
||||
and user.userprofile.gym != self.get_object().userprofile.gym
|
||||
):
|
||||
return HttpResponseForbidden()
|
||||
|
||||
return super(UserEditView, self).dispatch(request, *args, **kwargs)
|
||||
@@ -502,6 +507,7 @@ class UserDetailView(LoginRequiredMixin, WgerMultiplePermissionRequiredMixin, De
|
||||
"""
|
||||
User overview for gyms
|
||||
"""
|
||||
|
||||
model = User
|
||||
permission_required = ('gym.manage_gym', 'gym.manage_gyms', 'gym.gym_trainer')
|
||||
template_name = 'user/overview.html'
|
||||
@@ -519,9 +525,11 @@ class UserDetailView(LoginRequiredMixin, WgerMultiplePermissionRequiredMixin, De
|
||||
if not user.is_authenticated:
|
||||
return HttpResponseForbidden()
|
||||
|
||||
if (user.has_perm('gym.manage_gym') or user.has_perm('gym.gym_trainer')) \
|
||||
and not user.has_perm('gym.manage_gyms') \
|
||||
and user.userprofile.gym != self.get_object().userprofile.gym:
|
||||
if (
|
||||
(user.has_perm('gym.manage_gym') or user.has_perm('gym.gym_trainer'))
|
||||
and not user.has_perm('gym.manage_gyms')
|
||||
and user.userprofile.gym != self.get_object().userprofile.gym
|
||||
):
|
||||
return HttpResponseForbidden()
|
||||
|
||||
return super(UserDetailView, self).dispatch(request, *args, **kwargs)
|
||||
@@ -539,14 +547,16 @@ class UserDetailView(LoginRequiredMixin, WgerMultiplePermissionRequiredMixin, De
|
||||
{
|
||||
'workout': workout,
|
||||
'logs': logs.dates('date', 'day').count(),
|
||||
'last_log': logs.last()
|
||||
'last_log': logs.last(),
|
||||
}
|
||||
)
|
||||
context['workouts'] = out
|
||||
context['weight_entries'] = WeightEntry.objects.filter(user=self.object) \
|
||||
.order_by('-date')[:5]
|
||||
context['nutrition_plans'] = NutritionPlan.objects.filter(user=self.object) \
|
||||
.order_by('-creation_date')[:5]
|
||||
context['weight_entries'] = WeightEntry.objects.filter(user=self.object).order_by('-date')[
|
||||
:5
|
||||
]
|
||||
context['nutrition_plans'] = NutritionPlan.objects.filter(user=self.object).order_by(
|
||||
'-creation_date'
|
||||
)[:5]
|
||||
context['session'] = WorkoutSession.objects.filter(user=self.object).order_by('-date')[:10]
|
||||
context['admin_notes'] = AdminUserNote.objects.filter(member=self.object)[:5]
|
||||
context['contracts'] = Contract.objects.filter(member=self.object)[:5]
|
||||
@@ -563,8 +573,9 @@ 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):
|
||||
@@ -592,7 +603,7 @@ class UserListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
||||
_('Last activity'),
|
||||
_('Gym'),
|
||||
],
|
||||
'users': context['object_list']['members']
|
||||
'users': context['object_list']['members'],
|
||||
}
|
||||
return context
|
||||
|
||||
@@ -600,7 +611,7 @@ class UserListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
||||
class WgerPasswordChangeView(PasswordChangeView):
|
||||
template_name = 'form.html'
|
||||
success_url = reverse_lazy('core:user:preferences')
|
||||
title = gettext_lazy("Change password")
|
||||
title = gettext_lazy('Change password')
|
||||
|
||||
def get_form(self, form_class=None):
|
||||
form = super(WgerPasswordChangeView, self).get_form(form_class)
|
||||
@@ -611,8 +622,9 @@ class WgerPasswordChangeView(PasswordChangeView):
|
||||
Row(
|
||||
Column('new_password1', css_class='col-6'),
|
||||
Column('new_password2', css_class='col-6'),
|
||||
css_class='form-row'
|
||||
), ButtonHolder(Submit('submit', _("Save"), css_class='btn-success btn-block'))
|
||||
css_class='form-row',
|
||||
),
|
||||
ButtonHolder(Submit('submit', _('Save'), css_class='btn-success btn-block')),
|
||||
)
|
||||
return form
|
||||
|
||||
@@ -627,7 +639,7 @@ class WgerPasswordResetView(PasswordResetView):
|
||||
form = super(WgerPasswordResetView, self).get_form(form_class)
|
||||
form.helper = FormHelper()
|
||||
form.helper.form_class = 'wger-form'
|
||||
form.helper.add_input(Submit('submit', _("Save"), css_class='btn-success btn-block'))
|
||||
form.helper.add_input(Submit('submit', _('Save'), css_class='btn-success btn-block'))
|
||||
return form
|
||||
|
||||
|
||||
@@ -639,7 +651,7 @@ class WgerPasswordResetConfirmView(PasswordResetConfirmView):
|
||||
form = super(WgerPasswordResetConfirmView, self).get_form(form_class)
|
||||
form.helper = FormHelper()
|
||||
form.helper.form_class = 'wger-form'
|
||||
form.helper.add_input(Submit('submit', _("Save"), css_class='btn-success btn-block'))
|
||||
form.helper.add_input(Submit('submit', _('Save'), css_class='btn-success btn-block'))
|
||||
return form
|
||||
|
||||
|
||||
@@ -648,8 +660,7 @@ def confirm_email(request):
|
||||
if not request.user.userprofile.email_verified:
|
||||
send_email(request.user)
|
||||
messages.success(
|
||||
request,
|
||||
_('A verification email was sent to %(email)s') % {'email': request.user.email}
|
||||
request, _('A verification email was sent to %(email)s') % {'email': request.user.email}
|
||||
)
|
||||
|
||||
return HttpResponseRedirect(reverse('core:dashboard'))
|
||||
|
||||
@@ -50,6 +50,7 @@ class ListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
||||
"""
|
||||
Overview of all available weight units
|
||||
"""
|
||||
|
||||
model = WeightUnit
|
||||
permission_required = 'core.add_weightunit'
|
||||
template_name = 'weight_unit/list.html'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
EXERCISE_ENDPOINT = "exercisebaseinfo"
|
||||
DELETION_LOG_ENDPOINT = "deletion-log"
|
||||
CATEGORY_ENDPOINT = "exercisecategory"
|
||||
MUSCLE_ENDPOINT = "muscle"
|
||||
EQUIPMENT_ENDPOINT = "equipment"
|
||||
IMAGE_ENDPOINT = "exerciseimage"
|
||||
VIDEO_ENDPOINT = "video"
|
||||
EXERCISE_ENDPOINT = 'exercisebaseinfo'
|
||||
DELETION_LOG_ENDPOINT = 'deletion-log'
|
||||
CATEGORY_ENDPOINT = 'exercisecategory'
|
||||
MUSCLE_ENDPOINT = 'muscle'
|
||||
EQUIPMENT_ENDPOINT = 'equipment'
|
||||
IMAGE_ENDPOINT = 'exerciseimage'
|
||||
VIDEO_ENDPOINT = 'video'
|
||||
|
||||
@@ -35,7 +35,6 @@ class CanContributeExercises(BasePermission):
|
||||
DELETE_METHODS = ['DELETE']
|
||||
|
||||
def has_permission(self, request, view):
|
||||
|
||||
# Everybody can read
|
||||
if request.method in self.SAFE_METHODS:
|
||||
return True
|
||||
@@ -46,9 +45,8 @@ class CanContributeExercises(BasePermission):
|
||||
|
||||
# Creating or updating
|
||||
if request.method in self.ADD_METHODS:
|
||||
return (
|
||||
request.user.userprofile.is_trustworthy
|
||||
or request.user.has_perm('exercises.add_exercise')
|
||||
return request.user.userprofile.is_trustworthy or request.user.has_perm(
|
||||
'exercises.add_exercise'
|
||||
)
|
||||
|
||||
# Only admins are allowed to delete entries
|
||||
|
||||
@@ -88,6 +88,7 @@ class ExerciseImageSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
ExerciseImage serializer
|
||||
"""
|
||||
|
||||
author_history = serializers.ListSerializer(child=serializers.CharField(), read_only=True)
|
||||
exercise_base_uuid = serializers.ReadOnlyField(source='exercise_base.uuid')
|
||||
|
||||
@@ -115,6 +116,7 @@ class ExerciseVideoSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
ExerciseVideo serializer
|
||||
"""
|
||||
|
||||
exercise_base_uuid = serializers.ReadOnlyField(source='exercise_base.uuid')
|
||||
author_history = serializers.ListSerializer(child=serializers.CharField(), read_only=True)
|
||||
|
||||
@@ -147,6 +149,7 @@ class ExerciseVideoInfoSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
ExerciseVideo serializer for the info endpoint
|
||||
"""
|
||||
|
||||
author_history = serializers.ListSerializer(child=serializers.CharField(), read_only=True)
|
||||
|
||||
class Meta:
|
||||
@@ -245,6 +248,7 @@ class MuscleSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
Muscle serializer
|
||||
"""
|
||||
|
||||
image_url_main = serializers.CharField()
|
||||
image_url_secondary = serializers.CharField()
|
||||
|
||||
@@ -267,6 +271,7 @@ class ExerciseSerializer(serializers.ModelSerializer):
|
||||
The fields from the new ExerciseBase are retrieved here as to retain
|
||||
compatibility with the old model where all the fields where in Exercise.
|
||||
"""
|
||||
|
||||
category = serializers.PrimaryKeyRelatedField(queryset=ExerciseCategory.objects.all())
|
||||
muscles = serializers.PrimaryKeyRelatedField(many=True, queryset=Muscle.objects.all())
|
||||
muscles_secondary = serializers.PrimaryKeyRelatedField(many=True, queryset=Muscle.objects.all())
|
||||
@@ -277,21 +282,21 @@ class ExerciseSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Exercise
|
||||
fields = (
|
||||
"id",
|
||||
"uuid",
|
||||
"name",
|
||||
"exercise_base",
|
||||
"description",
|
||||
"created",
|
||||
"category",
|
||||
"muscles",
|
||||
"muscles_secondary",
|
||||
"equipment",
|
||||
"language",
|
||||
"license",
|
||||
"license_author",
|
||||
"variations",
|
||||
"author_history",
|
||||
'id',
|
||||
'uuid',
|
||||
'name',
|
||||
'exercise_base',
|
||||
'description',
|
||||
'created',
|
||||
'category',
|
||||
'muscles',
|
||||
'muscles_secondary',
|
||||
'equipment',
|
||||
'language',
|
||||
'license',
|
||||
'license_author',
|
||||
'variations',
|
||||
'author_history',
|
||||
)
|
||||
|
||||
|
||||
@@ -299,6 +304,7 @@ class ExerciseTranslationBaseInfoSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
Exercise translation serializer for the base info endpoint
|
||||
"""
|
||||
|
||||
id = serializers.IntegerField(required=False, read_only=True)
|
||||
uuid = serializers.UUIDField(required=False, read_only=True)
|
||||
exercise_base = serializers.PrimaryKeyRelatedField(
|
||||
@@ -312,22 +318,22 @@ class ExerciseTranslationBaseInfoSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Exercise
|
||||
fields = (
|
||||
"id",
|
||||
"uuid",
|
||||
"name",
|
||||
"exercise_base",
|
||||
"description",
|
||||
"created",
|
||||
"language",
|
||||
"aliases",
|
||||
"notes",
|
||||
"license",
|
||||
"license_title",
|
||||
"license_object_url",
|
||||
"license_author",
|
||||
"license_author_url",
|
||||
"license_derivative_source_url",
|
||||
"author_history",
|
||||
'id',
|
||||
'uuid',
|
||||
'name',
|
||||
'exercise_base',
|
||||
'description',
|
||||
'created',
|
||||
'language',
|
||||
'aliases',
|
||||
'notes',
|
||||
'license',
|
||||
'license_title',
|
||||
'license_object_url',
|
||||
'license_author',
|
||||
'license_author_url',
|
||||
'license_derivative_source_url',
|
||||
'author_history',
|
||||
)
|
||||
|
||||
|
||||
@@ -335,6 +341,7 @@ class ExerciseTranslationSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
Exercise translation serializer
|
||||
"""
|
||||
|
||||
id = serializers.IntegerField(required=False, read_only=True)
|
||||
uuid = serializers.UUIDField(required=False, read_only=True)
|
||||
exercise_base = serializers.PrimaryKeyRelatedField(
|
||||
@@ -345,13 +352,13 @@ class ExerciseTranslationSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Exercise
|
||||
fields = (
|
||||
"id",
|
||||
"uuid",
|
||||
"name",
|
||||
"exercise_base",
|
||||
"description",
|
||||
"created",
|
||||
"language",
|
||||
'id',
|
||||
'uuid',
|
||||
'name',
|
||||
'exercise_base',
|
||||
'description',
|
||||
'created',
|
||||
'language',
|
||||
'license_author',
|
||||
)
|
||||
|
||||
@@ -402,25 +409,25 @@ class ExerciseInfoSerializer(serializers.ModelSerializer):
|
||||
model = Exercise
|
||||
depth = 1
|
||||
fields = [
|
||||
"id",
|
||||
"name",
|
||||
"aliases",
|
||||
"uuid",
|
||||
"exercise_base_id",
|
||||
"description",
|
||||
"created",
|
||||
"category",
|
||||
"muscles",
|
||||
"muscles_secondary",
|
||||
"equipment",
|
||||
"language",
|
||||
"license",
|
||||
"license_author",
|
||||
"images",
|
||||
"videos",
|
||||
"comments",
|
||||
"variations",
|
||||
"author_history",
|
||||
'id',
|
||||
'name',
|
||||
'aliases',
|
||||
'uuid',
|
||||
'exercise_base_id',
|
||||
'description',
|
||||
'created',
|
||||
'category',
|
||||
'muscles',
|
||||
'muscles_secondary',
|
||||
'equipment',
|
||||
'language',
|
||||
'license',
|
||||
'license_author',
|
||||
'images',
|
||||
'videos',
|
||||
'comments',
|
||||
'variations',
|
||||
'author_history',
|
||||
]
|
||||
|
||||
|
||||
@@ -445,24 +452,24 @@ class ExerciseBaseInfoSerializer(serializers.ModelSerializer):
|
||||
model = ExerciseBase
|
||||
depth = 1
|
||||
fields = [
|
||||
"id",
|
||||
"uuid",
|
||||
"created",
|
||||
"last_update",
|
||||
"last_update_global",
|
||||
"category",
|
||||
"muscles",
|
||||
"muscles_secondary",
|
||||
"equipment",
|
||||
"license",
|
||||
"license_author",
|
||||
"images",
|
||||
"exercises",
|
||||
"variations",
|
||||
"images",
|
||||
"videos",
|
||||
"author_history",
|
||||
"total_authors_history",
|
||||
'id',
|
||||
'uuid',
|
||||
'created',
|
||||
'last_update',
|
||||
'last_update_global',
|
||||
'category',
|
||||
'muscles',
|
||||
'muscles_secondary',
|
||||
'equipment',
|
||||
'license',
|
||||
'license_author',
|
||||
'images',
|
||||
'exercises',
|
||||
'variations',
|
||||
'images',
|
||||
'videos',
|
||||
'author_history',
|
||||
'total_authors_history',
|
||||
]
|
||||
|
||||
def to_representation(self, instance):
|
||||
|
||||
@@ -99,9 +99,10 @@ class ExerciseBaseViewSet(ModelViewSet):
|
||||
|
||||
For a read-only endpoint with all the information of an exercise, see /api/v2/exercisebaseinfo/
|
||||
"""
|
||||
|
||||
queryset = ExerciseBase.translations.all()
|
||||
serializer_class = ExerciseBaseSerializer
|
||||
permission_classes = (CanContributeExercises, )
|
||||
permission_classes = (CanContributeExercises,)
|
||||
ordering_fields = '__all__'
|
||||
filterset_fields = (
|
||||
'category',
|
||||
@@ -148,8 +149,9 @@ class ExerciseTranslationViewSet(ModelViewSet):
|
||||
"""
|
||||
API endpoint for editing or adding exercise translation objects.
|
||||
"""
|
||||
|
||||
queryset = Exercise.objects.all()
|
||||
permission_classes = (CanContributeExercises, )
|
||||
permission_classes = (CanContributeExercises,)
|
||||
serializer_class = ExerciseTranslationSerializer
|
||||
ordering_fields = '__all__'
|
||||
filterset_fields = (
|
||||
@@ -171,7 +173,7 @@ class ExerciseTranslationViewSet(ModelViewSet):
|
||||
tags=HTML_TAG_WHITELIST,
|
||||
attributes=HTML_ATTRIBUTES_WHITELIST,
|
||||
css_sanitizer=CSSSanitizer(allowed_css_properties=HTML_STYLES_WHITELIST),
|
||||
strip=True
|
||||
strip=True,
|
||||
)
|
||||
super().perform_create(serializer)
|
||||
|
||||
@@ -200,7 +202,7 @@ class ExerciseTranslationViewSet(ModelViewSet):
|
||||
tags=HTML_TAG_WHITELIST,
|
||||
attributes=HTML_ATTRIBUTES_WHITELIST,
|
||||
css_sanitizer=CSSSanitizer(allowed_css_properties=HTML_STYLES_WHITELIST),
|
||||
strip=True
|
||||
strip=True,
|
||||
)
|
||||
|
||||
super().perform_update(serializer)
|
||||
@@ -217,8 +219,9 @@ class ExerciseViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
|
||||
This is only kept for backwards compatibility and will be removed in the future
|
||||
"""
|
||||
|
||||
queryset = Exercise.objects.all()
|
||||
permission_classes = (CanContributeExercises, )
|
||||
permission_classes = (CanContributeExercises,)
|
||||
serializer_class = ExerciseSerializer
|
||||
ordering_fields = '__all__'
|
||||
filterset_fields = (
|
||||
@@ -257,13 +260,13 @@ class ExerciseViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
try:
|
||||
qs = qs.filter(exercise_base__category_id=int(category))
|
||||
except ValueError:
|
||||
logger.info(f"Got {category} as category ID")
|
||||
logger.info(f'Got {category} as category ID')
|
||||
|
||||
if muscles:
|
||||
try:
|
||||
qs = qs.filter(exercise_base__muscles__in=[int(m) for m in muscles.split(',')])
|
||||
except ValueError:
|
||||
logger.info(f"Got {muscles} as muscle IDs")
|
||||
logger.info(f'Got {muscles} as muscle IDs')
|
||||
|
||||
if muscles_secondary:
|
||||
try:
|
||||
@@ -276,13 +279,13 @@ class ExerciseViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
try:
|
||||
qs = qs.filter(exercise_base__equipment__in=[int(e) for e in equipment.split(',')])
|
||||
except ValueError:
|
||||
logger.info(f"Got {equipment} as equipment IDs")
|
||||
logger.info(f'Got {equipment} as equipment IDs')
|
||||
|
||||
if license:
|
||||
try:
|
||||
qs = qs.filter(exercise_base__license_id=int(license))
|
||||
except ValueError:
|
||||
logger.info(f"Got {license} as license ID")
|
||||
logger.info(f'Got {license} as license ID')
|
||||
|
||||
return qs
|
||||
|
||||
@@ -306,14 +309,11 @@ class ExerciseViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
],
|
||||
# yapf: disable
|
||||
responses={
|
||||
200:
|
||||
inline_serializer(
|
||||
200: inline_serializer(
|
||||
name='ExerciseSearchResponse',
|
||||
fields={
|
||||
'value':
|
||||
CharField(),
|
||||
'data':
|
||||
inline_serializer(
|
||||
'value': CharField(),
|
||||
'data': inline_serializer(
|
||||
name='ExerciseSearchItemResponse',
|
||||
fields={
|
||||
'id': IntegerField(),
|
||||
@@ -321,12 +321,12 @@ class ExerciseViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
'name': CharField(),
|
||||
'category': CharField(),
|
||||
'image': CharField(),
|
||||
'image_thumbnail': CharField()
|
||||
}
|
||||
)
|
||||
}
|
||||
'image_thumbnail': CharField(),
|
||||
},
|
||||
),
|
||||
},
|
||||
)
|
||||
}
|
||||
},
|
||||
# yapf: enable
|
||||
)
|
||||
@api_view(['GET'])
|
||||
@@ -345,11 +345,12 @@ def search(request):
|
||||
return Response(response)
|
||||
|
||||
languages = [load_language(l) for l in language_codes.split(',')]
|
||||
translations = Exercise.objects \
|
||||
.filter(Q(name__icontains=q) | Q(alias__alias__icontains=q)) \
|
||||
.filter(language__in=languages) \
|
||||
.order_by('exercise_base__category__name', 'name') \
|
||||
translations = (
|
||||
Exercise.objects.filter(Q(name__icontains=q) | Q(alias__alias__icontains=q))
|
||||
.filter(language__in=languages)
|
||||
.order_by('exercise_base__category__name', 'name')
|
||||
.distinct()
|
||||
)
|
||||
|
||||
for translation in translations:
|
||||
image = None
|
||||
@@ -372,8 +373,8 @@ def search(request):
|
||||
'name': translation.name,
|
||||
'category': _(translation.category.name),
|
||||
'image': image,
|
||||
'image_thumbnail': thumbnail
|
||||
}
|
||||
'image_thumbnail': thumbnail,
|
||||
},
|
||||
}
|
||||
results.append(result_json)
|
||||
response['suggestions'] = results
|
||||
@@ -436,10 +437,11 @@ class EquipmentViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
"""
|
||||
API endpoint for equipment objects
|
||||
"""
|
||||
|
||||
queryset = Equipment.objects.all()
|
||||
serializer_class = EquipmentSerializer
|
||||
ordering_fields = '__all__'
|
||||
filterset_fields = ('name', )
|
||||
filterset_fields = ('name',)
|
||||
|
||||
@method_decorator(cache_page(settings.WGER_SETTINGS['EXERCISE_CACHE_TTL']))
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
@@ -454,20 +456,22 @@ class DeletionLogViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
as well when performing a sync (e.g. because many exercises where submitted at
|
||||
once or an image was uploaded that hasn't a CC license)
|
||||
"""
|
||||
|
||||
queryset = DeletionLog.objects.all()
|
||||
serializer_class = DeletionLogSerializer
|
||||
ordering_fields = '__all__'
|
||||
filterset_fields = ('model_type', )
|
||||
filterset_fields = ('model_type',)
|
||||
|
||||
|
||||
class ExerciseCategoryViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
"""
|
||||
API endpoint for exercise categories objects
|
||||
"""
|
||||
|
||||
queryset = ExerciseCategory.objects.all()
|
||||
serializer_class = ExerciseCategorySerializer
|
||||
ordering_fields = '__all__'
|
||||
filterset_fields = ('name', )
|
||||
filterset_fields = ('name',)
|
||||
|
||||
@method_decorator(cache_page(settings.WGER_SETTINGS['EXERCISE_CACHE_TTL']))
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
@@ -481,7 +485,7 @@ class ExerciseImageViewSet(ModelViewSet):
|
||||
|
||||
queryset = ExerciseImage.objects.all()
|
||||
serializer_class = ExerciseImageSerializer
|
||||
permission_classes = (CanContributeExercises, )
|
||||
permission_classes = (CanContributeExercises,)
|
||||
ordering_fields = '__all__'
|
||||
filterset_fields = (
|
||||
'is_main',
|
||||
@@ -509,7 +513,7 @@ class ExerciseImageViewSet(ModelViewSet):
|
||||
t = get_thumbnailer(image.image)
|
||||
thumbnails[alias] = {
|
||||
'url': t.get_thumbnail(aliases.get(alias)).url,
|
||||
'settings': aliases.get(alias)
|
||||
'settings': aliases.get(alias),
|
||||
}
|
||||
thumbnails['original'] = image.image.url
|
||||
return Response(thumbnails)
|
||||
@@ -541,9 +545,10 @@ class ExerciseVideoViewSet(ModelViewSet):
|
||||
"""
|
||||
API endpoint for exercise video objects
|
||||
"""
|
||||
|
||||
queryset = ExerciseVideo.objects.all()
|
||||
serializer_class = ExerciseVideoSerializer
|
||||
permission_classes = (CanContributeExercises, )
|
||||
permission_classes = (CanContributeExercises,)
|
||||
ordering_fields = '__all__'
|
||||
filterset_fields = (
|
||||
'is_main',
|
||||
@@ -579,8 +584,9 @@ class ExerciseCommentViewSet(ModelViewSet):
|
||||
"""
|
||||
API endpoint for exercise comment objects
|
||||
"""
|
||||
|
||||
serializer_class = ExerciseCommentSerializer
|
||||
permission_classes = (CanContributeExercises, )
|
||||
permission_classes = (CanContributeExercises,)
|
||||
ordering_fields = '__all__'
|
||||
filterset_fields = ('comment', 'exercise')
|
||||
|
||||
@@ -620,9 +626,10 @@ class ExerciseAliasViewSet(ModelViewSet):
|
||||
"""
|
||||
API endpoint for exercise aliases objects
|
||||
"""
|
||||
|
||||
serializer_class = ExerciseAliasSerializer
|
||||
queryset = Alias.objects.all()
|
||||
permission_classes = (CanContributeExercises, )
|
||||
permission_classes = (CanContributeExercises,)
|
||||
ordering_fields = '__all__'
|
||||
filterset_fields = ('alias', 'exercise')
|
||||
|
||||
@@ -653,15 +660,17 @@ class ExerciseVariationViewSet(ModelViewSet):
|
||||
"""
|
||||
API endpoint for exercise variation objects
|
||||
"""
|
||||
|
||||
serializer_class = ExerciseVariationSerializer
|
||||
queryset = Variation.objects.all()
|
||||
permission_classes = (CanContributeExercises, )
|
||||
permission_classes = (CanContributeExercises,)
|
||||
|
||||
|
||||
class MuscleViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
"""
|
||||
API endpoint for muscle objects
|
||||
"""
|
||||
|
||||
queryset = Muscle.objects.all()
|
||||
serializer_class = MuscleSerializer
|
||||
ordering_fields = '__all__'
|
||||
|
||||
@@ -20,12 +20,13 @@ from django.apps import AppConfig
|
||||
|
||||
class ExerciseConfig(AppConfig):
|
||||
name = 'wger.exercises'
|
||||
verbose_name = "Exercise"
|
||||
verbose_name = 'Exercise'
|
||||
|
||||
def ready(self):
|
||||
import wger.exercises.signals
|
||||
|
||||
from actstream import registry
|
||||
|
||||
registry.register(self.get_model('Alias'))
|
||||
registry.register(self.get_model('Exercise'))
|
||||
registry.register(self.get_model('ExerciseBase'))
|
||||
|
||||
@@ -26,7 +26,6 @@ from wger.exercises.models import (
|
||||
|
||||
|
||||
class ExerciseImageForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = ExerciseImage
|
||||
fields = (
|
||||
@@ -39,7 +38,6 @@ class ExerciseImageForm(forms.ModelForm):
|
||||
|
||||
|
||||
class ExerciseVideoForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = ExerciseVideo
|
||||
fields = (
|
||||
@@ -50,7 +48,6 @@ class ExerciseVideoForm(forms.ModelForm):
|
||||
|
||||
|
||||
class CommentForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = ExerciseComment
|
||||
exclude = ('exercise', )
|
||||
exclude = ('exercise',)
|
||||
|
||||
@@ -40,7 +40,7 @@ class Command(BaseCommand):
|
||||
'--exercise-base-id',
|
||||
action='store',
|
||||
dest='exercise_base_id',
|
||||
help='The ID of the exercise base'
|
||||
help='The ID of the exercise base',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--exercise-id', action='store', dest='exercise_id', help='The ID of the exercise'
|
||||
@@ -77,7 +77,7 @@ class Command(BaseCommand):
|
||||
exercise.license_author = author_name
|
||||
exercise.save()
|
||||
|
||||
self.stdout.write(self.style.SUCCESS(f"Exercise and/or exercise base has been updated"))
|
||||
self.stdout.write(self.style.SUCCESS(f'Exercise and/or exercise base has been updated'))
|
||||
|
||||
def print_error(self, error_message):
|
||||
self.stdout.write(self.style.WARNING(f"{error_message}"))
|
||||
self.stdout.write(self.style.WARNING(f'{error_message}'))
|
||||
|
||||
@@ -38,7 +38,6 @@ class Command(BaseCommand):
|
||||
"""
|
||||
|
||||
def handle(self, **options):
|
||||
|
||||
# Collect all exercise bases that are not used in any workout or log entry
|
||||
out = f'{ExerciseBase.objects.all().count()} exercises currently in the database'
|
||||
self.stdout.write(out)
|
||||
|
||||
@@ -52,11 +52,10 @@ class Command(BaseCommand):
|
||||
dest='remote_url',
|
||||
default=settings.WGER_SETTINGS['WGER_INSTANCE'],
|
||||
help=f'Remote URL to fetch the images from (default: WGER_SETTINGS'
|
||||
f'["WGER_INSTANCE"] - {settings.WGER_SETTINGS["WGER_INSTANCE"]})'
|
||||
f'["WGER_INSTANCE"] - {settings.WGER_SETTINGS["WGER_INSTANCE"]})',
|
||||
)
|
||||
|
||||
def handle(self, **options):
|
||||
|
||||
if not settings.MEDIA_ROOT:
|
||||
raise ImproperlyConfigured('Please set MEDIA_ROOT in your settings file')
|
||||
|
||||
|
||||
@@ -44,12 +44,10 @@ class Command(BaseCommand):
|
||||
action='store',
|
||||
dest='remote_url',
|
||||
default='https://wger.de',
|
||||
help='Remote URL to fetch the exercises from (default: '
|
||||
'https://wger.de)'
|
||||
help='Remote URL to fetch the exercises from (default: ' 'https://wger.de)',
|
||||
)
|
||||
|
||||
def handle(self, **options):
|
||||
|
||||
if not settings.MEDIA_ROOT:
|
||||
raise ImproperlyConfigured('Please set MEDIA_ROOT in your settings file')
|
||||
|
||||
|
||||
@@ -40,13 +40,12 @@ class Command(BaseCommand):
|
||||
|
||||
languages = Language.objects.all()
|
||||
for base in ExerciseBase.objects.all():
|
||||
|
||||
data = {
|
||||
'base': {
|
||||
'uuid': base.uuid,
|
||||
'category': base.category.name,
|
||||
'equipment': ','.join([e.name for e in base.equipment.all()]),
|
||||
'variations': base.variations.id if base.variations else ''
|
||||
'variations': base.variations.id if base.variations else '',
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +56,7 @@ class Command(BaseCommand):
|
||||
'description': '',
|
||||
'aliases': '',
|
||||
'license': '',
|
||||
'author': ''
|
||||
'author': '',
|
||||
}
|
||||
|
||||
exercise = Exercise.objects.filter(exercise_base=base, language=language).first()
|
||||
@@ -73,7 +72,9 @@ class Command(BaseCommand):
|
||||
out.append(data)
|
||||
|
||||
with open('exercise_cleanup.csv', 'w', newline='') as csvfile:
|
||||
file_writer = csv.writer(csvfile, )
|
||||
file_writer = csv.writer(
|
||||
csvfile,
|
||||
)
|
||||
|
||||
header = ['base:uuid', 'base:category', 'base:equipment', 'base:variations']
|
||||
for language in languages:
|
||||
|
||||
@@ -29,14 +29,17 @@ class Command(BaseCommand):
|
||||
"""
|
||||
Performs some sanity checks on the exercise database
|
||||
"""
|
||||
|
||||
english: Language
|
||||
|
||||
help = "Performs some sanity checks on the exercise database. " \
|
||||
"At the moment this script checks that each exercise:\n" \
|
||||
"- has at least one translation\n" \
|
||||
"- has a translation in English\n" \
|
||||
"- has no duplicate translations\n\n" \
|
||||
"Each problem can be fixed individually by using the --delete-* flags\n"
|
||||
help = (
|
||||
'Performs some sanity checks on the exercise database. '
|
||||
'At the moment this script checks that each exercise:\n'
|
||||
'- has at least one translation\n'
|
||||
'- has a translation in English\n'
|
||||
'- has no duplicate translations\n\n'
|
||||
'Each problem can be fixed individually by using the --delete-* flags\n'
|
||||
)
|
||||
|
||||
def create_parser(self, *args, **kwargs):
|
||||
parser = super(Command, self).create_parser(*args, **kwargs)
|
||||
@@ -44,14 +47,13 @@ class Command(BaseCommand):
|
||||
return parser
|
||||
|
||||
def add_arguments(self, parser):
|
||||
|
||||
parser.add_argument(
|
||||
'--delete-untranslated',
|
||||
action='store_true',
|
||||
dest='delete_untranslated',
|
||||
default=False,
|
||||
help="Delete exercises without translations (safe to use since these can't be "
|
||||
"accessed over the UI)",
|
||||
'accessed over the UI)',
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
@@ -80,7 +82,6 @@ class Command(BaseCommand):
|
||||
)
|
||||
|
||||
def handle(self, **options):
|
||||
|
||||
delete_untranslated = options['delete_untranslated'] or options['delete_all']
|
||||
delete_duplicates = options['delete_duplicates'] or options['delete_all']
|
||||
delete_no_english = options['delete_no_english'] or options['delete_all']
|
||||
@@ -120,7 +121,8 @@ class Command(BaseCommand):
|
||||
exercise_languages = base.exercises.values_list('language', flat=True)
|
||||
duplicates = [
|
||||
Language.objects.get(pk=item)
|
||||
for item, count in collections.Counter(exercise_languages).items() if count > 1
|
||||
for item, count in collections.Counter(exercise_languages).items()
|
||||
if count > 1
|
||||
]
|
||||
|
||||
if not duplicates:
|
||||
|
||||
@@ -63,7 +63,7 @@ class Command(BaseCommand):
|
||||
action='store_true',
|
||||
dest='process_videos',
|
||||
default=False,
|
||||
help='Flag indicating whether to process and add videos to the exercises'
|
||||
help='Flag indicating whether to process and add videos to the exercises',
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
@@ -71,7 +71,7 @@ class Command(BaseCommand):
|
||||
action='store_true',
|
||||
dest='create_on_new',
|
||||
default=True,
|
||||
help="Controls whether we create new bases or exercises if they have the UUID 'NEW'"
|
||||
help="Controls whether we create new bases or exercises if they have the UUID 'NEW'",
|
||||
)
|
||||
|
||||
def handle(self, **options):
|
||||
@@ -91,8 +91,9 @@ class Command(BaseCommand):
|
||||
for language in [l.short_name for l in languages]:
|
||||
for column in columns:
|
||||
name = '{0}:{1}'.format(language, column)
|
||||
assert (name in file_reader.fieldnames
|
||||
), '{0} not in {1}'.format(name, file_reader.fieldnames)
|
||||
assert name in file_reader.fieldnames, '{0} not in {1}'.format(
|
||||
name, file_reader.fieldnames
|
||||
)
|
||||
|
||||
default_license = License.objects.get(pk=CC_BY_SA_4_ID)
|
||||
|
||||
@@ -121,10 +122,14 @@ class Command(BaseCommand):
|
||||
self.stdout.write(f' Skipping creating new exercise base...\n')
|
||||
continue
|
||||
|
||||
base = ExerciseBase.objects.get_or_create(
|
||||
uuid=base_uuid,
|
||||
defaults={'category': ExerciseCategory.objects.get(name=base_category)}
|
||||
)[0] if not new_base else ExerciseBase()
|
||||
base = (
|
||||
ExerciseBase.objects.get_or_create(
|
||||
uuid=base_uuid,
|
||||
defaults={'category': ExerciseCategory.objects.get(name=base_category)},
|
||||
)[0]
|
||||
if not new_base
|
||||
else ExerciseBase()
|
||||
)
|
||||
|
||||
# Update the base data
|
||||
base.category = ExerciseCategory.objects.get(name=base_category)
|
||||
@@ -200,12 +205,13 @@ class Command(BaseCommand):
|
||||
if not new_translation:
|
||||
continue
|
||||
|
||||
translation = Exercise.objects.get_or_create(
|
||||
uuid=exercise_uuid, defaults={
|
||||
'exercise_base': base,
|
||||
'language': language
|
||||
}
|
||||
)[0] if not new_translation else Exercise()
|
||||
translation = (
|
||||
Exercise.objects.get_or_create(
|
||||
uuid=exercise_uuid, defaults={'exercise_base': base, 'language': language}
|
||||
)[0]
|
||||
if not new_translation
|
||||
else Exercise()
|
||||
)
|
||||
translation.exercise_base = base
|
||||
translation.language = language
|
||||
translation.name = exercise_name
|
||||
@@ -218,8 +224,9 @@ class Command(BaseCommand):
|
||||
exercise_license = License.objects.get(short_name=exercise_license)
|
||||
except License.DoesNotExist:
|
||||
self.stdout.write(
|
||||
self.style.
|
||||
WARNING(f' License does not exist: {exercise_license}!!!\n')
|
||||
self.style.WARNING(
|
||||
f' License does not exist: {exercise_license}!!!\n'
|
||||
)
|
||||
)
|
||||
exercise_license = default_license
|
||||
else:
|
||||
|
||||
@@ -29,7 +29,6 @@ class Command(BaseCommand):
|
||||
help = 'Read out the user submitted exercise'
|
||||
|
||||
def handle(self, **options):
|
||||
|
||||
exercises = Exercise.objects.all()
|
||||
usernames = []
|
||||
for exercise in exercises:
|
||||
|
||||
@@ -37,6 +37,7 @@ class Command(BaseCommand):
|
||||
"""
|
||||
Synchronizes exercise data from a wger instance to the local database
|
||||
"""
|
||||
|
||||
remote_url = settings.WGER_SETTINGS['WGER_INSTANCE']
|
||||
|
||||
help = """Synchronizes exercise data from a wger instance to the local database.
|
||||
@@ -58,7 +59,7 @@ class Command(BaseCommand):
|
||||
dest='remote_url',
|
||||
default=settings.WGER_SETTINGS['WGER_INSTANCE'],
|
||||
help=f'Remote URL to fetch the exercises from (default: WGER_SETTINGS'
|
||||
f'["WGER_INSTANCE"] - {settings.WGER_SETTINGS["WGER_INSTANCE"]})'
|
||||
f'["WGER_INSTANCE"] - {settings.WGER_SETTINGS["WGER_INSTANCE"]})',
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
@@ -66,11 +67,10 @@ class Command(BaseCommand):
|
||||
action='store_true',
|
||||
dest='skip_delete',
|
||||
default=False,
|
||||
help='Skips deleting any entries'
|
||||
help='Skips deleting any entries',
|
||||
)
|
||||
|
||||
def handle(self, **options):
|
||||
|
||||
remote_url = options['remote_url']
|
||||
|
||||
try:
|
||||
|
||||
@@ -31,7 +31,7 @@ class Command(BaseCommand):
|
||||
'--exercise-base-id',
|
||||
action='store',
|
||||
dest='exercise_base_id',
|
||||
help='The ID of the exercise base, otherwise all exercises will be updated'
|
||||
help='The ID of the exercise base, otherwise all exercises will be updated',
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
@@ -39,7 +39,7 @@ class Command(BaseCommand):
|
||||
action='store_true',
|
||||
dest='force',
|
||||
default=False,
|
||||
help='Force the update of the cache'
|
||||
help='Force the update of the cache',
|
||||
)
|
||||
|
||||
def handle(self, **options):
|
||||
@@ -56,9 +56,9 @@ class Command(BaseCommand):
|
||||
|
||||
def handle_cache(self, exercise: ExerciseBase, force: bool):
|
||||
if force:
|
||||
self.stdout.write(f"Force updating cache for exercise base {exercise.uuid}")
|
||||
self.stdout.write(f'Force updating cache for exercise base {exercise.uuid}')
|
||||
else:
|
||||
self.stdout.write(f"Warming cache for exercise base {exercise.uuid}")
|
||||
self.stdout.write(f'Warming cache for exercise base {exercise.uuid}')
|
||||
|
||||
if force:
|
||||
reset_exercise_api_cache(exercise.uuid)
|
||||
|
||||
@@ -6,7 +6,6 @@ import wger.exercises.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0001_initial'),
|
||||
]
|
||||
@@ -19,14 +18,14 @@ class Migration(migrations.Migration):
|
||||
'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',
|
||||
@@ -35,18 +34,17 @@ class Migration(migrations.Migration):
|
||||
'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.',
|
||||
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
|
||||
)
|
||||
blank=True,
|
||||
),
|
||||
),
|
||||
(
|
||||
'status',
|
||||
@@ -54,27 +52,27 @@ class Migration(migrations.Migration):
|
||||
default=b'1',
|
||||
max_length=2,
|
||||
editable=False,
|
||||
choices=[(b'1', 'Pending'), (b'2', 'Accepted'), (b'3', 'Declined')]
|
||||
)
|
||||
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)]
|
||||
)
|
||||
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)
|
||||
models.DateField(auto_now_add=True, verbose_name='Date', null=True),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'ordering': ['name'],
|
||||
},
|
||||
bases=(models.Model, ),
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ExerciseCategory',
|
||||
@@ -83,7 +81,7 @@ class Migration(migrations.Migration):
|
||||
'id',
|
||||
models.AutoField(
|
||||
verbose_name='ID', serialize=False, auto_created=True, primary_key=True
|
||||
)
|
||||
),
|
||||
),
|
||||
('name', models.CharField(max_length=100, verbose_name='Name')),
|
||||
],
|
||||
@@ -91,7 +89,7 @@ class Migration(migrations.Migration):
|
||||
'ordering': ['name'],
|
||||
'verbose_name_plural': 'Exercise Categories',
|
||||
},
|
||||
bases=(models.Model, ),
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ExerciseComment',
|
||||
@@ -100,15 +98,15 @@ class Migration(migrations.Migration):
|
||||
'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'
|
||||
)
|
||||
verbose_name='Comment',
|
||||
),
|
||||
),
|
||||
(
|
||||
'exercise',
|
||||
@@ -116,12 +114,12 @@ class Migration(migrations.Migration):
|
||||
editable=False,
|
||||
to='exercises.Exercise',
|
||||
verbose_name='Exercise',
|
||||
on_delete=models.CASCADE
|
||||
)
|
||||
on_delete=models.CASCADE,
|
||||
),
|
||||
),
|
||||
],
|
||||
options={},
|
||||
bases=(models.Model, ),
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ExerciseImage',
|
||||
@@ -130,18 +128,17 @@ class Migration(migrations.Migration):
|
||||
'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.',
|
||||
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
|
||||
)
|
||||
blank=True,
|
||||
),
|
||||
),
|
||||
(
|
||||
'status',
|
||||
@@ -149,31 +146,30 @@ class Migration(migrations.Migration):
|
||||
default=b'1',
|
||||
max_length=2,
|
||||
editable=False,
|
||||
choices=[(b'1', 'Pending'), (b'2', 'Accepted'), (b'3', 'Declined')]
|
||||
)
|
||||
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.image.exercise_image_upload_dir,
|
||||
verbose_name='Image'
|
||||
)
|
||||
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'
|
||||
)
|
||||
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',
|
||||
@@ -181,14 +177,14 @@ class Migration(migrations.Migration):
|
||||
default=2,
|
||||
verbose_name='License',
|
||||
to='core.License',
|
||||
on_delete=models.CASCADE
|
||||
)
|
||||
on_delete=models.CASCADE,
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-is_main', 'id'],
|
||||
},
|
||||
bases=(models.Model, ),
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Muscle',
|
||||
@@ -197,22 +193,22 @@ class Migration(migrations.Migration):
|
||||
'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'
|
||||
)
|
||||
verbose_name='Name',
|
||||
),
|
||||
),
|
||||
('is_front', models.BooleanField(default=1)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['name'],
|
||||
},
|
||||
bases=(models.Model, ),
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='exercise',
|
||||
@@ -262,7 +258,7 @@ class Migration(migrations.Migration):
|
||||
null=True,
|
||||
verbose_name='Secondary muscles',
|
||||
to='exercises.Muscle',
|
||||
blank=True
|
||||
blank=True,
|
||||
),
|
||||
preserve_default=True,
|
||||
),
|
||||
|
||||
@@ -11,14 +11,13 @@ def generate_uuids(apps, schema_editor):
|
||||
:param schema_editor:
|
||||
:return:
|
||||
"""
|
||||
Excercise = apps.get_model("exercises", "Exercise")
|
||||
Excercise = apps.get_model('exercises', 'Exercise')
|
||||
for exercise in Excercise.objects.all():
|
||||
exercise.uuid = uuid.uuid4()
|
||||
exercise.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('exercises', '0001_initial'),
|
||||
]
|
||||
|
||||
@@ -8,7 +8,7 @@ def copy_name(apps, schema_editor):
|
||||
"""
|
||||
Copies the exercise name to the original name field
|
||||
"""
|
||||
Excercise = apps.get_model("exercises", "Exercise")
|
||||
Excercise = apps.get_model('exercises', 'Exercise')
|
||||
for exercise in Excercise.objects.all():
|
||||
exercise.name_original = exercise.name
|
||||
exercise.save()
|
||||
@@ -31,14 +31,13 @@ def capitalize_name(apps, schema_editor):
|
||||
out.append(word)
|
||||
return ' '.join(out)
|
||||
|
||||
Excercise = apps.get_model("exercises", "Exercise")
|
||||
Excercise = apps.get_model('exercises', 'Exercise')
|
||||
for exercise in Excercise.objects.all():
|
||||
exercise.name = capitalize(exercise.name_original)
|
||||
exercise.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('exercises', '0002_auto_20150307_1841'),
|
||||
]
|
||||
|
||||
@@ -6,7 +6,6 @@ import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('exercises', '0003_auto_20160921_2000'),
|
||||
]
|
||||
@@ -33,7 +32,7 @@ class Migration(migrations.Migration):
|
||||
blank=True,
|
||||
related_name='secondary_muscles',
|
||||
to='exercises.Muscle',
|
||||
verbose_name='Secondary muscles'
|
||||
verbose_name='Secondary muscles',
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
@@ -43,7 +42,7 @@ class Migration(migrations.Migration):
|
||||
choices=[('1', 'Pending'), ('2', 'Accepted'), ('3', 'Declined')],
|
||||
default='1',
|
||||
editable=False,
|
||||
max_length=2
|
||||
max_length=2,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
@@ -56,9 +55,8 @@ class Migration(migrations.Migration):
|
||||
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'
|
||||
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(
|
||||
@@ -68,7 +66,7 @@ class Migration(migrations.Migration):
|
||||
choices=[('1', 'Pending'), ('2', 'Accepted'), ('3', 'Declined')],
|
||||
default='1',
|
||||
editable=False,
|
||||
max_length=2
|
||||
max_length=2,
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -5,7 +5,6 @@ from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('exercises', '0004_auto_20170404_0114'),
|
||||
]
|
||||
@@ -13,16 +12,10 @@ 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']},
|
||||
),
|
||||
]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user