Exemple #1
0
class ResultSerializer(QueryFieldsMixin, serializers.ModelSerializer):
    """
    Serializer for results
    """
    dry_run = serializers.BooleanField(required=False)
    partial = ResultPartialNestedSerializer(many=True, required=False)
    permissions = DRYPermissionsField()

    class Meta:
        model = Result
        fields = ('id', 'competition', 'athlete', 'team_members', 'first_name',
                  'last_name', 'organization', 'category',
                  'elimination_category', 'result', 'decimals', 'result_code',
                  'position', 'position_pre', 'approved', 'info', 'team',
                  'partial', 'permissions', 'dry_run')

    def create(self, validated_data):
        """
        Nested partial results support in create
        """
        partial_data = validated_data.pop('partial', None)
        team_members = validated_data.pop('team_members', None)
        dry_run = validated_data.pop('dry_run', None)
        if not dry_run:
            result = Result.objects.create(**validated_data)
        else:
            result = Result(**validated_data)
        if partial_data:
            for partial in partial_data:
                if not dry_run:
                    ResultPartial.objects.create(result=result, **partial)
        if team_members and not dry_run:
            result.team_members.set(team_members)
        return result

    def update(self, instance, validated_data):
        """
        Nested partial results support in update
        """
        dry_run = validated_data.pop('dry_run', None)
        if not dry_run:
            for data in validated_data:
                if data not in ['partial', 'team_members']:
                    setattr(instance, data, validated_data[data])
            if 'team_members' in validated_data:
                team_members = validated_data.pop('team_members')
                instance.team_members.set(team_members)
            instance.save()
            partial_existing = list(
                ResultPartial.objects.filter(result=instance).values_list(
                    'id', flat=True))
            if 'partial' in validated_data:
                partial_data = validated_data.pop('partial')
                for partial in partial_data:
                    try:
                        partial_instance = ResultPartial.objects.get(
                            result=instance,
                            type=partial['type'],
                            order=partial['order'])
                        for data in partial:
                            if data not in ['result', 'type', 'order']:
                                setattr(partial_instance, data, partial[data])
                        partial_instance.save()
                        partial_existing.remove(partial_instance.pk)
                    except ResultPartial.DoesNotExist:
                        ResultPartial.objects.create(result=instance,
                                                     **partial)
                ResultPartial.objects.filter(pk__in=partial_existing).delete()
        return instance

    @staticmethod
    def _age_difference(competition, athlete, exact):
        """
        Returns athlete's age at the time of competition.
        """
        if exact:
            return competition.date_start.year - athlete.date_of_birth.year -\
                   ((competition.date_start.month, competition.date_start.day) <
                    (athlete.date_of_birth.month, athlete.date_of_birth.day))
        else:
            return competition.date_start.year - athlete.date_of_birth.year

    def _check_max_age(self, competition, category, athlete):
        """
        Validates athletes maximum age
        """
        if (athlete.date_of_birth and
            ((not category.age_exact and self._age_difference(
                competition, athlete, category.age_exact) > category.max_age)
             or (category.age_exact and
                 self._age_difference(competition, athlete, category.age_exact)
                 >= category.max_age))):
            raise serializers.ValidationError(
                _("Athlete is too old for this category."))

    def _check_min_age(self, competition, category, athlete):
        """
        Validates athletes minimum age
        """
        if (athlete.date_of_birth and self._age_difference(
                competition, athlete, category.age_exact) < category.min_age):
            raise serializers.ValidationError(
                _("Athlete is too young for this category."))

    def _check_age(self, competition, category, athletes):
        """
        Validates category's age requirements.
        """
        if category.min_age:
            for athlete in athletes:
                self._check_min_age(competition, category, athlete)
        if category.max_age:
            for athlete in athletes:
                self._check_max_age(competition, category, athlete)

    @staticmethod
    def _check_gender(category, athletes):
        """
        Validates category's gender requirement.
        """
        for athlete in athletes:
            if ((category.gender == "W" and athlete.gender == "M")
                    or (category.gender == "M" and athlete.gender == "W")):
                raise serializers.ValidationError(
                    _("Athlete's gender is not allowed for this category."))

    @staticmethod
    def _check_number_of_team_members(category, athletes):
        """
        Validates the number of team members.
        """
        if category.team and category.team_size and len(
                athletes) != category.team_size:
            raise serializers.ValidationError(
                _("Incorrect number of team members for this category."))

    @staticmethod
    def _check_requirements(competition, athletes):
        """
        Validates competition level and type requirements for the athletes.
        i.e. licence.
        """
        for requirement in competition.type.requirements.split(
                ',') + competition.level.requirements.split(','):
            if requirement.strip():
                for athlete in athletes:
                    if not athlete.organization.external and not athlete.info.filter(
                            type=requirement.strip(),
                            date_start__lte=competition.date_start,
                            date_end__gte=competition.date_start).count():
                        raise serializers.ValidationError(
                            _("Missing requirement: %s." %
                              requirement.strip()))

    def _check_value_limits(self, result, category, competition_type):
        """
        Validates result total limits.
        """
        max_result, min_result = self._get_result_limits(
            category, competition_type)
        if category.team and category.team_size and max_result:
            max_result = max_result * category.team_size
        if min_result is not None and result < min_result:
            raise serializers.ValidationError(_('A result is too low.'))
        if max_result is not None and result > max_result:
            raise serializers.ValidationError(_('A result is too high.'))

    @staticmethod
    def _check_team(data):
        """
        Validates team information.
        """
        if 'team' in data and data['team'] and not data['category'].team:
            raise serializers.ValidationError(
                _("Incorrect category for a team result."))
        if 'athlete' in data and data['athlete'] and data['category'].team:
            raise serializers.ValidationError(
                _("Use team_members instead of athlete field for teams."))
        if 'team_members' not in data and data['category'].team:
            raise serializers.ValidationError(
                _("Team result without team_members."))

    def _get_category(self, data):
        """
        Returns the result category in data or old instance.
        """
        if 'category' in data:
            category = data['category']
        elif self.instance:
            category = self.instance.category
        else:
            raise serializers.ValidationError(_("Missing category."))
        return category

    def _get_athletes(self, data):
        """
        Returns the list of athletes in a result and if a result is team.
        """
        athletes = []
        if (self.instance and self.instance.team) or ('team' in data
                                                      and data['team']):
            team = True
            if 'team_members' in data:
                athletes = data['team_members']
            elif self.instance:
                athletes = self.instance.team_members.all()
            else:
                raise serializers.ValidationError(_("Missing athletes."))
        else:
            team = False
            if 'athlete' in data and data['athlete']:
                athletes = [data['athlete']]
            elif self.instance:
                athletes = [self.instance.athlete]
        if not athletes:
            raise serializers.ValidationError(_("Missing athletes."))
        return athletes, team

    def _get_competition(self, data):
        """
        Returns the competition in data or old instance.
        """
        if 'competition' in data:
            competition = data['competition']
        elif self.instance:
            competition = self.instance.competition
        else:
            raise serializers.ValidationError(_("Missing competition."))
        return competition

    def _get_result(self, data):
        """
        Returns the result total in data or old instance.
        """
        if 'result' in data:
            result = data['result']
        elif self.instance:
            result = self.instance.result
        else:
            result = None
        return result

    @staticmethod
    def _get_result_limits(category, competition_type):
        """
        Returns result limits for the competition type and category.

        Raises ValidationError if category is not allowed for the competition
        type.
        """
        check = CategoryForCompetitionType.objects.filter(
            category=category, type=competition_type).first()
        if check and check.disallow:
            raise serializers.ValidationError(
                _("Category is not allowed for this competition type."))
        max_result = check.max_result if check and check.max_result else competition_type.max_result
        min_result = check.min_result if check and check.min_result else competition_type.min_result
        return max_result, min_result

    def _check_existence(self, data, category):
        """
        Raises ValidationError if trying to create new result and it already exists.
        """
        if self.instance is None and (
            (not category.team
             and Result.objects.filter(competition=data['competition'],
                                       athlete=data['athlete'],
                                       category=data['category'])) or
            (category.team
             and Result.objects.filter(competition=data['competition'],
                                       last_name=data['last_name'],
                                       category=data['category']))):
            raise serializers.ValidationError(_('Entry already exists.'))

    def _check_team_status(self, data):
        """
        Raises ValidationError if team status is changed. This is done because of the validation and record checks.
        """
        if self.instance and (
            ('team' in data and data['team'] and not self.instance.team) or
            (self.instance.team and 'team' in data and not data['team'])):
            raise serializers.ValidationError(_('Cannot change team status.'))

    @staticmethod
    def _check_partial(data, competition, category):
        """
        Validate partial results
        """
        if 'partial' in data and len(data['partial']):
            for partial in data['partial']:
                if partial['type'].competition_type != competition.type:
                    raise serializers.ValidationError(
                        _('Partial result type does not match competition type.'
                          ))
                if 'value' in partial and partial[
                        'type'].min_result is not None and partial[
                            'value'] < partial['type'].min_result:
                    raise serializers.ValidationError(
                        _('A result is too low.'))
                if 'value' in partial and partial[
                        'type'].max_result is not None:
                    max_result = partial['type'].max_result
                    if category.team and category.team_size:
                        max_result = max_result * category.team_size
                    if partial['value'] > max_result:
                        raise serializers.ValidationError(
                            _('A result is too high.'))

    def validate(self, data):
        """
        Validates:
         - permissions to create or modify the result
         - team status
         - duplicates
         - competition level and type requirements for the athlete
         - number of team members in result
         - gender limitations for the category
         - age limitations for the category
         - value limits for the result
         - partial results
        """
        if (not (self.context['request'].user.is_superuser
                 or self.context['request'].user.is_staff) and
            ((self.instance and
              (self.instance.competition.locked or self.instance.approved or
               (self.instance.competition.level.require_approval
                and not self.instance.competition.approved))) or
             (data['competition'].locked or
              (data['competition'].level.require_approval
               and not data['competition'].approved) or
              ('approved' in data and data['approved'])
              or data['competition'].organization.group
              not in self.context['request'].user.groups.all()))):
            raise serializers.ValidationError(
                _('No permission to alter or create a record.'), 403)
        self._check_team_status(data)
        athletes, team = self._get_athletes(data)
        category = self._get_category(data)
        competition = self._get_competition(data)
        self._check_existence(data, category)
        if settings.CHECK_COMPETITION_REQUIREMENTS:
            self._check_requirements(competition, athletes)
        self._check_number_of_team_members(category, athletes)
        self._check_gender(category, athletes)
        self._check_age(competition, category, athletes)
        result = self._get_result(data)
        if result is not None:
            self._check_value_limits(result, category, competition.type)
        self._check_partial(data, competition, category)
        if 'approved' in data and data['approved'] and not (
                self.context['request'].user.is_superuser
                or self.context['request'].user.is_staff):
            raise serializers.ValidationError(
                _("No permission to approve results."))
        return data