Some more tweaks for the exercise submission serializer

This commit is contained in:
Roland Geider
2025-09-28 13:34:16 +02:00
parent b080bbb96f
commit 5e36e25fb2
2 changed files with 140 additions and 57 deletions

View File

@@ -421,12 +421,11 @@ class ExerciseTranslationSubmissionSerializer(serializers.ModelSerializer):
if detected_language_code != language.short_name.lower():
raise serializers.ValidationError(
{
'language':
f'The detected language of the description is "{detected_language.name.capitalize()}" '
f'({detected_language_code}), which does not match your selected language: '
f'"{language.full_name.capitalize()}" ({language.short_name}). If you believe '
f'this is incorrect, try adding more content or rephrasing your text, as '
f'language detection works better with longer or more complete sentences.'
'language': f'The detected language of the description is "{detected_language.name.capitalize()}" '
f'({detected_language_code}), which does not match your selected language: '
f'"{language.full_name.capitalize()}" ({language.short_name}). If you believe '
f'this is incorrect, try adding more content or rephrasing your text, as '
f'language detection works better with longer or more complete sentences.'
}
)
@@ -585,7 +584,11 @@ class ExerciseSubmissionSerializer(serializers.ModelSerializer):
muscles = serializers.PrimaryKeyRelatedField(queryset=Muscle.objects.all(), many=True)
muscles_secondary = serializers.PrimaryKeyRelatedField(queryset=Muscle.objects.all(), many=True)
equipment = serializers.PrimaryKeyRelatedField(queryset=Equipment.objects.all(), many=True)
variation = serializers.PrimaryKeyRelatedField(queryset=Variation.objects.all(), required=False)
variations = serializers.PrimaryKeyRelatedField(
queryset=Variation.objects.all(),
required=False,
allow_null=True,
)
license = serializers.PrimaryKeyRelatedField(
queryset=License.objects.all(),
required=False,
@@ -601,13 +604,29 @@ class ExerciseSubmissionSerializer(serializers.ModelSerializer):
'muscles',
'muscles_secondary',
'equipment',
'variation',
'variations',
'license',
'license_author',
'translations',
]
model = Exercise
def validate(self, data):
# Ensure at least one translation is present
translations = data.get('translations', [])
if not data.get('translations', []):
raise serializers.ValidationError(
{'translations': 'You must provide at least one translation.'}
)
# At least one translation in English
if not any(t.get('language').short_name == 'en' for t in translations):
raise serializers.ValidationError(
{'translations': 'You must provide at least one translation in English.'}
)
return data
@transaction.atomic
def create(self, validated_data):
# Create the Exercise object first
@@ -615,7 +634,7 @@ class ExerciseSubmissionSerializer(serializers.ModelSerializer):
category=validated_data.pop('category'),
license=validated_data.pop('license'),
license_author=validated_data.pop('license_author'),
variations=validated_data.pop('variation', None),
variations=validated_data.pop('variations', None),
)
exercise.muscles.set(validated_data.pop('muscles'))
exercise.muscles_secondary.set(validated_data.pop('muscles_secondary'))

View File

@@ -13,29 +13,36 @@
# You should have received a copy of the GNU Affero General Public License
# along with Workout Manager. If not, see <http://www.gnu.org/licenses/>.
# Standard Library
from collections import namedtuple
# Third Party
from rest_framework import status
# wger
from wger.core.tests.api_base_test import ApiBaseTestCase
from wger.core.tests.base_testcase import BaseTestCase
from wger.exercises.models import Exercise
from wger.exercises.models import (
Alias,
Exercise,
ExerciseComment,
Translation,
)
class SearchSubmissionApiTestCase(BaseTestCase, ApiBaseTestCase):
url = '/api/v2/exercise-submission/'
def test_successful_submission(self):
self.authenticate('admin')
payload = {
@staticmethod
def get_payload():
return {
'category': 3,
'license': 5,
'muscles': [3, 4],
'muscles_secondary': [1],
'equipment': [3],
'license_author': 'test man',
'variation': 1,
'variations': 1,
'translations': [
{
'name': '1-Arm Half-Kneeling Lat Pulldown',
@@ -44,11 +51,12 @@ class SearchSubmissionApiTestCase(BaseTestCase, ApiBaseTestCase):
'license_author': 'tester',
'aliases': [
{'alias': 'This is another name'},
{'alias': 'ashda dsads asa dssa'},
{'alias': 'yet another name'},
],
'comments': [
{'comment': 'This is a very important note'},
{'comment': 'Do the exercise correctly'},
{'comment': 'the third comment'},
],
},
{
@@ -60,53 +68,109 @@ class SearchSubmissionApiTestCase(BaseTestCase, ApiBaseTestCase):
],
}
count_before = Exercise.objects.count()
response = self.client.post(self.url, data=payload)
count_after = Exercise.objects.count()
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(response.json().get('id'), 9)
self.assertEqual(count_before + 1, count_after)
@staticmethod
def get_counts():
Counts = namedtuple('Counts', ['exercise', 'translation', 'alias', 'comment'])
def test_unsuccessful_submission(self):
return Counts(
Exercise.objects.count(),
Translation.objects.count(),
Alias.objects.count(),
ExerciseComment.objects.count(),
)
def test_successful_submission_full(self):
"""Test that all objects were correctly created."""
self.authenticate('admin')
before = self.get_counts()
response = self.client.post(self.url, data=self.get_payload())
after = self.get_counts()
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
exercise = Exercise.objects.get(pk=response.json().get('id'))
self.assertEqual(exercise.category_id, 3)
self.assertEqual(list(exercise.muscles.values_list('id', flat=True)), [3, 4])
self.assertEqual(list(exercise.muscles_secondary.values_list('id', flat=True)), [1])
self.assertEqual(list(exercise.equipment.values_list('id', flat=True)), [3])
self.assertEqual(exercise.license_author, 'test man')
self.assertEqual(exercise.variations_id, 1)
self.assertEqual(before.exercise + 1, after.exercise)
self.assertEqual(before.translation + 2, after.translation)
self.assertEqual(before.alias + 2, after.alias)
self.assertEqual(before.comment + 3, after.comment)
def test_successful_submission_not_all_fields(self):
"""Test that all objects were correctly created even if not all fields are present."""
self.authenticate('admin')
payload = self.get_payload()
payload['muscles_secondary'] = []
payload['muscles'] = []
payload['equipment'] = []
payload['variations'] = None
before = self.get_counts()
response_data = self.client.post(self.url, data=payload).json()
after = self.get_counts()
print(response_data)
exercise = Exercise.objects.get(pk=response_data.get('id'))
self.assertEqual(exercise.category_id, 3)
self.assertEqual(list(exercise.muscles.values_list('id', flat=True)), [])
self.assertEqual(list(exercise.muscles_secondary.values_list('id', flat=True)), [])
self.assertEqual(list(exercise.equipment.values_list('id', flat=True)), [])
self.assertEqual(exercise.license_author, 'test man')
self.assertEqual(exercise.variations_id, None)
self.assertEqual(before.exercise + 1, after.exercise)
self.assertEqual(before.translation + 2, after.translation)
self.assertEqual(before.alias + 2, after.alias)
self.assertEqual(before.comment + 3, after.comment)
def test_unsuccessful_submission_no_translations(self):
"""
If any part of the exercise submission fails, no exercise is created.
"""
self.authenticate('admin')
payload = {
'category': 100,
'license': 5,
'muscles': [3, 4],
'muscles_secondary': [1],
'equipment': [3],
'license_author': 'test man',
'variation': 1,
'translations': [
{
'name': '1-Arm Half-Kneeling Lat Pulldown',
'description': 'Attach a D-Handle to a high pully. And use your lat muscles to pull the weight single handedly.',
'language': 2,
'license_author': 'tester',
'aliases': [
{'alias': 'This is another name'},
{'alias': 'ashda dsads asa dssa'},
],
'comments': [
{'comment': 'This is a very important note'},
{'comment': 'Do the exercise correctly'},
],
},
{
'name': '2 Handed Kettlebell Swing',
'description': '<p>das ist die Beschreibung für die Übung</p>',
'language': 1,
'license_author': 'tester',
},
],
}
payload = self.get_payload()
payload['translations'] = []
count_before = Exercise.objects.count()
counts_before = self.get_counts()
response = self.client.post(self.url, data=payload)
count_after = Exercise.objects.count()
response_data = response.json()
counts_after = self.get_counts()
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(count_before, count_after)
self.assertEqual(counts_before, counts_after)
self.assertEqual(
response_data.get('translations'), ['You must provide at least one translation.']
)
def test_unsuccessful_submission_no_english_translations(self):
"""
If any part of the exercise submission fails, no exercise is created.
-> An exercise must have at least one English translation.
"""
self.authenticate('admin')
payload = self.get_payload()
del payload['translations'][0]
counts_before = self.get_counts()
response = self.client.post(self.url, data=payload)
response_data = response.json()
counts_after = self.get_counts()
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(counts_before, counts_after)
self.assertEqual(
response_data.get('translations'),
['You must provide at least one translation in English.'],
)