Skip to content

Commit

Permalink
Create function for saving sub-serializers
Browse files Browse the repository at this point in the history
There is a common pattern for creating/updating objects in the API for
nested objects, overriding `validated_data`. This commit creates a
helper function to avoid repeating.

This also fixes failures caused by not overriding the "_errors"
attribute.
  • Loading branch information
tienne-B committed Nov 29, 2024
1 parent 9d51b9a commit 4a2ac1d
Showing 1 changed file with 41 additions and 88 deletions.
129 changes: 41 additions & 88 deletions tabbycat/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ def _validate_field(self, field, value):
return value


def save_related(serializer, data, context, save_fields):
s = serializer(many=isinstance(data, list), context=context)
s._validated_data = data
s._errors = []
s.save(**save_fields)


class RootSerializer(serializers.Serializer):
class RootLinksSerializer(serializers.Serializer):
v1 = serializers.HyperlinkedIdentityField(view_name='api-v1-root')
Expand Down Expand Up @@ -238,13 +245,8 @@ def create(self, validated_data):

round = super().create(validated_data)

if len(motions_data) > 0:
for i, motion in enumerate(motions_data, start=1):
motion['seq'] = i

motions = self.RoundMotionSerializer(many=True, context=self.context)
motions._validated_data = motions_data # Data was already validated
motions.save(round=round)
for i, motion in enumerate(motions_data, start=1):
save_related(self.RoundMotionSerializer, motion, self.context, {'round': round, 'seq': i})

return round

Expand Down Expand Up @@ -359,10 +361,7 @@ def create(self, validated_data):
rounds_data = validated_data.pop('roundmotion_set')
motion = super().create(validated_data)

if len(rounds_data) > 0:
rounds = self.RoundsSerializer(many=True, context=self.context)
rounds._validated_data = rounds_data # Data was already validated
rounds.save(motion=motion)
save_related(self.RoundsSerializer, rounds_data, self.context, {'motion': motion})

return motion

Expand Down Expand Up @@ -622,10 +621,7 @@ def create(self, validated_data):

adj = super().create(validated_data)

if len(venue_constraints) > 0:
vc = VenueConstraintSerializer(many=True, context=self.context)
vc._validated_data = venue_constraints # Data was already validated
vc.save(subject=adj)
save_related(VenueConstraintSerializer, venue_constraints, self.context, {'subject': adj})

if url_key is None: # If explicitly null (and not just an empty string)
populate_url_keys([adj])
Expand All @@ -639,11 +635,7 @@ def create(self, validated_data):
return adj

def update(self, instance, validated_data):
venue_constraints = validated_data.pop('venue_constraints', [])
if len(venue_constraints) > 0:
vc = VenueConstraintSerializer(many=True, context=self.context)
vc._validated_data = venue_constraints # Data was already validated
vc.save(subject=instance)
save_related(VenueConstraintSerializer, validated_data.pop('venue_constraints', []), self.context, {'subject': instance})

if 'base_score' in validated_data and validated_data['base_score'] != instance.base_score:
AdjudicatorBaseScoreHistory.objects.create(
Expand Down Expand Up @@ -768,32 +760,17 @@ def create(self, validated_data):
).exclude(pk__in=[bc.pk for bc in break_categories])) + break_categories)

# The data is passed to the sub-serializer so that it handles categories
if len(speakers_data) > 0:
speakers = SpeakerSerializer(many=True, context=self.context)
speakers._validated_data = speakers_data # Data was already validated
speakers.save(team=team)

if len(venue_constraints) > 0:
vc = VenueConstraintSerializer(many=True, context=self.context)
vc._validated_data = venue_constraints # Data was already validated
vc.save(subject=team)
save_related(SpeakerSerializer, speakers_data, self.context, {'team': team})
save_related(VenueConstraintSerializer, venue_constraints, self.context, {'subject': team})

if team.institution is not None:
team.teaminstitutionconflict_set.get_or_create(institution=team.institution)

return team

def update(self, instance, validated_data):
speakers_data = validated_data.pop('speakers', [])
venue_constraints = validated_data.pop('venue_constraints', [])
if len(speakers_data) > 0:
speakers = SpeakerSerializer(many=True, context=self.context)
speakers._validated_data = speakers_data # Data was already validated
speakers.save(team=instance)
if len(venue_constraints) > 0:
vc = VenueConstraintSerializer(many=True, context=self.context)
vc._validated_data = venue_constraints # Data was already validated
vc.save(subject=instance)
save_related(SpeakerSerializer, validated_data.pop('speakers', []), self.context, {'team': instance})
save_related(VenueConstraintSerializer, validated_data.pop('venue_constraints', []), self.context, {'subject': instance})

if self.partial:
# Avoid removing conflicts if merely PATCHing
Expand Down Expand Up @@ -824,20 +801,12 @@ def create(self, validated_data):

institution = super().create(validated_data)

if len(venue_constraints) > 0:
vc = VenueConstraintSerializer(many=True, context=self.context)
vc._validated_data = venue_constraints # Data was already validated
vc.save(subject=institution)
save_related(VenueConstraintSerializer, venue_constraints, self.context, {'subject': institution})

return institution

def update(self, instance, validated_data):
venue_constraints = validated_data.pop('venue_constraints', [])
if len(venue_constraints) > 0:
vc = VenueConstraintSerializer(many=True, context=self.context)
vc._validated_data = venue_constraints # Data was already validated
vc.save(subject=instance)

save_related(VenueConstraintSerializer, validated_data.pop('venue_constraints', []), self.context, {'subject': instance})
return super().update(instance, validated_data)


Expand Down Expand Up @@ -970,7 +939,9 @@ class Meta:
fields = ('team', 'side')

def save(self, **kwargs):
kwargs['side'] = kwargs.get('side', kwargs['seq'])
seq = kwargs.pop('seq')
if 'side' not in self.validated_data:
self.validated_data['side'] = kwargs.get('side', seq)
return super().save(**kwargs)

class PairingLinksSerializer(serializers.Serializer):
Expand Down Expand Up @@ -1007,15 +978,11 @@ def create(self, validated_data):
validated_data['round'] = self.context['round']
debate = super().create(validated_data)

teams = self.DebateTeamSerializer()
for i, team in enumerate(teams_data):
teams._validated_data = teams_data # Data was already validated
teams.save(debate=debate, seq=i)
save_related(self.DebateTeamSerializer, team, self.context, {'debate': debate, 'seq': i})

if adjs_data is not None:
adjudicators = DebateAdjudicatorSerializer()
adjudicators._validated_data = adjs_data
adjudicators.save(debate=debate)
save_related(DebateAdjudicatorSerializer, adjs_data, self.context, {'debate': debate})

return debate

Expand All @@ -1028,10 +995,8 @@ def update(self, instance, validated_data):
except (IntegrityError, TypeError) as e:
raise serializers.ValidationError(e)

if 'adjudicators' in validated_data and validated_data['adjudicators'] is not None:
adjudicators = DebateAdjudicatorSerializer()
adjudicators._validated_data = validated_data.pop('adjudicators')
adjudicators.save(debate=instance)
if (adjs_data := validated_data.pop('adjudicators', None)) is not None:
save_related(DebateAdjudicatorSerializer, adjs_data, self.context, {'debate': instance})

return super().update(instance, validated_data)

Expand Down Expand Up @@ -1300,15 +1265,13 @@ def save(self, **kwargs):
args.insert(0, kwargs.get('adjudicator'))
result.add_winner(*args)

speech_serializer = self.SpeechSerializer(context=self.context)
for i, speech in enumerate(self.validated_data.get('speeches', []), 1):
speech_serializer._validated_data = speech
speech_serializer.save(
result=result,
side=side,
seq=i,
adjudicator=kwargs.get('adjudicator'),
)
save_related(self.SpeechSerializer, speech, self.context, {
'result': result,
'side': side,
'seq': i,
'adjudicator': kwargs.get('adjudicator'),
})
return result

teams = TeamResultSerializer(many=True)
Expand All @@ -1335,10 +1298,12 @@ def validate_teams(self, value):
return value

def save(self, **kwargs):
team_serializer = self.TeamResultSerializer(context=self.context)
for i, team in enumerate(self.validated_data.get('teams', [])):
team_serializer._validated_data = team
team_serializer.save(result=kwargs['result'], adjudicator=self.validated_data.get('adjudicator'), seq=i)
save_related(self.TeamResultSerializer, team, self.context, {
'result': kwargs['result'],
'adjudicator': self.validated_data.get('adjudicator'),
'seq': i,
})
return kwargs['result']

sheets = SheetSerializer(many=True, required=True)
Expand All @@ -1359,10 +1324,8 @@ def validate(self, data):
def create(self, validated_data):
result = DebateResult(validated_data['ballot'], tournament=self.context.get('tournament'))

sheets = self.SheetSerializer(context=self.context)
for sheet in validated_data['sheets']:
sheets._validated_data = sheet
sheets.save(result=result)
save_related(self.SheetSerializer, sheet, self.context, {'result': result})

try:
result.save()
Expand Down Expand Up @@ -1455,16 +1418,10 @@ def create(self, validated_data):

ballot = super().create(validated_data)

result = self.ResultSerializer(context=self.context)
result._validated_data = result_data
result._errors = []
result.save(ballot=ballot)
save_related(self.ResultSerializer, result_data, self.context, {'ballot': ballot})

if veto_data:
vetos = self.VetoSerializer(context=self.context, many=True)
vetos._validated_data = veto_data
vetos._errors = []
vetos.save(ballot_submission=ballot, preference=3)
save_related(self.VetoSerializer, veto_data, self.context, {'ballot_submission': ballot, 'preference': 3})

return ballot

Expand Down Expand Up @@ -1501,17 +1458,13 @@ def create(self, validated_data):
debate = super().create(validated_data)

if adjs_data is not None:
adjudicators = DebateAdjudicatorSerializer()
adjudicators._validated_data = adjs_data
adjudicators.save(debate=debate)
save_related(DebateAdjudicatorSerializer, adjs_data, self.context, {'debate': debate})

return debate

def update(self, instance, validated_data):
if validated_data.get('adjudicators', None) is not None:
adjudicators = DebateAdjudicatorSerializer()
adjudicators._validated_data = validated_data.pop('adjudicators')
adjudicators.save(debate=instance)
save_related(DebateAdjudicatorSerializer, validated_data.pop('adjudicators'), self.context, {'debate': instance})

return super().update(instance, validated_data)

Expand Down

0 comments on commit 4a2ac1d

Please sign in to comment.