serializers.py 8.96 KB
Newer Older
1
"""Serializers for the workouts application."""
asmundh's avatar
asmundh committed
2
3
from rest_framework import serializers
from rest_framework.serializers import HyperlinkedRelatedField
4
5
6
7
8
9

from workouts.models import Exercise
from workouts.models import ExerciseInstance
from workouts.models import RememberMe
from workouts.models import Workout
from workouts.models import WorkoutFile
asmundh's avatar
asmundh committed
10
11
12


class ExerciseInstanceSerializer(serializers.HyperlinkedModelSerializer):
13
14
    """Serializer for an ExerciseInstance. Hyperlinks are used for
    relationships by default.
asmundh's avatar
asmundh committed
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

    Serialized fields: url, id, exercise, sets, number, workout

    Attributes:
        workout:    The associated workout for this instance, represented by a hyperlink
    """

    workout = HyperlinkedRelatedField(
        queryset=Workout.objects.all(), view_name="workout-detail", required=False
    )

    class Meta:
        model = ExerciseInstance
        fields = ["url", "id", "exercise", "sets", "number", "workout"]


class WorkoutFileSerializer(serializers.HyperlinkedModelSerializer):
32
33
    """Serializer for a WorkoutFile. Hyperlinks are used for relationships by
    default.
asmundh's avatar
asmundh committed
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

    Serialized fields: url, id, owner, file, workout

    Attributes:
        owner:      The owner (User) of the WorkoutFile, represented by a username. ReadOnly
        workout:    The associate workout for this WorkoutFile, represented by a hyperlink
    """

    owner = serializers.ReadOnlyField(source="owner.username")
    workout = HyperlinkedRelatedField(
        queryset=Workout.objects.all(), view_name="workout-detail", required=False
    )

    class Meta:
        model = WorkoutFile
        fields = ["url", "id", "owner", "file", "workout"]

    def create(self, validated_data):
        return WorkoutFile.objects.create(**validated_data)


class WorkoutSerializer(serializers.HyperlinkedModelSerializer):
56
57
    """Serializer for a Workout. Hyperlinks are used for relationships by
    default.
asmundh's avatar
asmundh committed
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91

    This serializer specifies nested serialization since a workout consists of WorkoutFiles
    and ExerciseInstances.

    Serialized fields: url, id, name, date, notes, owner, owner_username, visiblity,
                       exercise_instances, files

    Attributes:
        owner_username:     Username of the owning User
        exercise_instance:  Serializer for ExericseInstances
        files:              Serializer for WorkoutFiles
    """

    owner_username = serializers.SerializerMethodField()
    exercise_instances = ExerciseInstanceSerializer(many=True, required=True)
    files = WorkoutFileSerializer(many=True, required=False)

    class Meta:
        model = Workout
        fields = [
            "url",
            "id",
            "name",
            "date",
            "notes",
            "owner",
            "owner_username",
            "visibility",
            "exercise_instances",
            "files",
        ]
        extra_kwargs = {"owner": {"read_only": True}}

    def create(self, validated_data):
92
93
        """Custom logic for creating ExerciseInstances, WorkoutFiles, and a
        Workout.
asmundh's avatar
asmundh committed
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120

        This is needed to iterate over the files and exercise instances, since this serializer is
        nested.

        Args:
            validated_data: Validated files and exercise_instances

        Returns:
            Workout: A newly created Workout
        """
        exercise_instances_data = validated_data.pop("exercise_instances")
        files_data = []
        if "files" in validated_data:
            files_data = validated_data.pop("files")

        workout = Workout.objects.create(**validated_data)

        for exercise_instance_data in exercise_instances_data:
            ExerciseInstance.objects.create(workout=workout, **exercise_instance_data)
        for file_data in files_data:
            WorkoutFile.objects.create(
                workout=workout, owner=workout.owner, file=file_data.get("file")
            )

        return workout

    def update(self, instance, validated_data):
121
122
        """Custom logic for updating a Workout with its ExerciseInstances and
        Workouts.
asmundh's avatar
asmundh committed
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142

        This is needed because each object in both exercise_instances and files must be iterated
        over and handled individually.

        Args:
            instance (Workout): Current Workout object
            validated_data: Contains data for validated fields

        Returns:
            Workout: Updated Workout instance
        """
        exercise_instances_data = validated_data.pop("exercise_instances")
        exercise_instances = instance.exercise_instances

        instance.name = validated_data.get("name", instance.name)
        instance.notes = validated_data.get("notes", instance.notes)
        instance.visibility = validated_data.get("visibility", instance.visibility)
        instance.date = validated_data.get("date", instance.date)
        instance.save()

and's avatar
and committed
143
144
145
146
147
148
149
150
151
        self.update_exercise_instance(exercise_instances_data, exercise_instances)
        self.update_or_delete_exercise(
            exercise_instances_data, exercise_instances, instance
        )
        self.iterate_workout_files(instance, validated_data)

        return instance

    def update_exercise_instance(self, exercise_instances_data, exercise_instances):
asmundh's avatar
asmundh committed
152
153
154
155
156
157
158
159
160
161
162
163
164
165
        for exercise_instance, exercise_instance_data in zip(
            exercise_instances.all(), exercise_instances_data
        ):
            exercise_instance.exercise = exercise_instance_data.get(
                "exercise", exercise_instance.exercise
            )
            exercise_instance.number = exercise_instance_data.get(
                "number", exercise_instance.number
            )
            exercise_instance.sets = exercise_instance_data.get(
                "sets", exercise_instance.sets
            )
            exercise_instance.save()

and's avatar
and committed
166
167
168
    def update_or_delete_exercise(
        self, exercise_instances_data, exercise_instances, instance
    ):
asmundh's avatar
asmundh committed
169
170
171
172
173
174
        if len(exercise_instances_data) > len(exercise_instances.all()):
            for i in range(len(exercise_instances.all()), len(exercise_instances_data)):
                exercise_instance_data = exercise_instances_data[i]
                ExerciseInstance.objects.create(
                    workout=instance, **exercise_instance_data
                )
and's avatar
and committed
175

asmundh's avatar
asmundh committed
176
177
178
179
        elif len(exercise_instances_data) < len(exercise_instances.all()):
            for i in range(len(exercise_instances_data), len(exercise_instances.all())):
                exercise_instances.all()[i].delete()

and's avatar
and committed
180
    def iterate_workout_files(self, instance, validated_data):
asmundh's avatar
asmundh committed
181
182
183
184
185
186
187
188
189
190
191
192
193
194
        if "files" in validated_data:
            files_data = validated_data.pop("files")
            files = instance.files

            for file, file_data in zip(files.all(), files_data):
                file.file = file_data.get("file", file.file)

            if len(files_data) > len(files.all()):
                for i in range(len(files.all()), len(files_data)):
                    WorkoutFile.objects.create(
                        workout=instance,
                        owner=instance.owner,
                        file=files_data[i].get("file"),
                    )
and's avatar
and committed
195

asmundh's avatar
asmundh committed
196
197
198
199
200
            elif len(files_data) < len(files.all()):
                for i in range(len(files_data), len(files.all())):
                    files.all()[i].delete()

    def get_owner_username(self, obj):
201
        """Returns the owning user's username.
asmundh's avatar
asmundh committed
202
203
204
205
206
207
208
209
210
211
212

        Args:
            obj (Workout): Current Workout

        Returns:
            str: Username of owner
        """
        return obj.owner.username


class ExerciseSerializer(serializers.HyperlinkedModelSerializer):
213
214
    """Serializer for an Exercise. Hyperlinks are used for relationships by
    default.
asmundh's avatar
asmundh committed
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231

    Serialized fields: url, id, name, description, unit, instances

    Attributes:
        instances:  Associated exercise instances with this Exercise type. Hyperlinks.
    """

    instances = serializers.HyperlinkedRelatedField(
        many=True, view_name="exerciseinstance-detail", read_only=True
    )

    class Meta:
        model = Exercise
        fields = ["url", "id", "name", "description", "unit", "instances"]


class RememberMeSerializer(serializers.HyperlinkedModelSerializer):
232
233
    """Serializer for an RememberMe. Hyperlinks are used for relationships by
    default.
asmundh's avatar
asmundh committed
234
235
236
237
238
239
240
241
242
243

    Serialized fields: remember_me

    Attributes:
        remember_me:    Value of cookie used for remember me functionality
    """

    class Meta:
        model = RememberMe
        fields = ["remember_me"]
244
245
246
247
248
249


class HighScoreSerializer(serializers.Serializer):
    """Serializer for a HighScore.

    Attributes:
250
251
        score: The number of workouts completed.
        units: The number of units performed for an exercise.
252
253
254
255
256
        username: The username of the athlete holding this high score.
        workouts: The list of workouts related to this high score.
    """

    score = serializers.IntegerField()
257
    units = serializers.IntegerField()
258
259
260
261
262
263
    username = serializers.SerializerMethodField()
    workouts = WorkoutSerializer(many=True)

    def get_username(self, highscore):
        """Returns the athlete's username."""
        return highscore.athlete.username