class Log(models.Model):
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, null=True, blank=True)
    object_id = models.PositiveIntegerField(null=True, blank=True)
    content_object = GenericForeignKey('content_type', 'object_id')

    user = models.ForeignKey(User, null=True, blank=True)
    action = models.CharField(max_length=50, blank=True)
    message = models.CharField(max_length=255, blank=True)
    params = JSONField(blank=True, null=True)

    created = models.DateTimeField(auto_now_add=True)
    modified = models.DateTimeField(auto_now=True)

    def set_message(self, message, commit=True):
        self.message = message
        if commit:
            self.save()

    def set_object(self, obj, commit=True):
        self.content_object = obj
        if commit:
            self.save() class Distance(TimestampMixin, models.Model):
    competition = models.ForeignKey('core.Competition')
    name = models.CharField(max_length=100)
    distance_text = models.CharField(max_length=50, blank=True)
    distance_m = models.IntegerField(blank=True, null=True, help_text='Distance in meters')
    can_have_teams = models.BooleanField(default=True)
    have_results = models.BooleanField(default=True)

    profile_price = models.DecimalField(max_digits=20, decimal_places=2, blank=True, null=True)
    kind = models.CharField(max_length=2, blank=True)  # We need kind to map distances between competitions

    class Meta:
        order_with_respect_to = 'competition'

    def __str__(self):
        return self.name

    def get_participants(self, competition_ids):
        return self.participant_set.filter(competition_id__in=competition_ids)

    def get_teams(self):
        return self.team_set.all() def get_image(path, width=1*cm):
    def __init__(self, name, parent=None, **kw):
    def _setKwds(self,**kw):
    def __repr__(self):
    def refresh(self):
    def listAttrs(self, indent=''):
    def clone(self, name, parent=None, **kwds):
    def prepareCanvas(self, canvas):
    def __init__(self):
    def __getitem__(self, key):
    def get(self,key,default=_stylesheet1_undefined):
    def __contains__(self, key):
    def has_key(self,key):
    def add(self, style, alias=None):
    def list(self):
def testStyles():
def getSampleStyleSheet():
    def __init__(self, *args, **kwargs):
    def showPage(self):
    def save(self):
    def draw_page_number(self, page_count):
    def __init__(self, *args, **kwargs):
    def showPage(self):
    def save(self):
    def draw_footer(self):
def fill_page_with_image(path, canvas):
class PropertySet:
class ParagraphStyle(PropertySet):
class LineStyle(PropertySet):
class ListStyle(PropertySet):
class StyleSheet1:
class PageNumCanvas(canvas.Canvas):
class InvoiceCanvas(canvas.Canvas):
    ORIENTATIONS = {
        1: "Horizontal (normal)",
        2: "Mirrored horizontal",
        3: "Rotated 180",
        4: "Mirrored vertical",
        5: "Mirrored horizontal then rotated 90 CCW",
        6: "Rotated 90 CW",
        7: "Mirrored horizontal then rotated 90 CW",
        8: "Rotated 90 CCW",
    } class SEBCompetitionBase(CompetitionScriptBase):

    CALCULATE_STANDING_FROM_STAGES = 5

    def __init__(self, *args, **kwargs):
        """
        Current competition have multiple stages. We need to set current stage index.
        """
        super(SEBCompetitionBase, self).__init__(*args, **kwargs)

        if self.competition.level == 2:  # if class is created for parent competition, then we do not have index
            classname = self.__class__.__name__
            cache_key = '%s_competition_index_ids' % classname
            child_ids = cache.get(cache_key)
            if not child_ids:
                child_ids = [c.id for c in self.competition.parent.get_children()]
                cache.set(cache_key, child_ids)

            self.competition_index = child_ids.index(self.competition_id) + 1


    def build_manager_menu(self):
        child_items = []
        for child in self.competition.get_children():
            children = []
            children.append(item('Dalībnieki', '#', url_as_pattern=False, access_loggedin=True, in_menu=False, children=[
                item('Pieteikt dalībnieku', 'manager:participant_create %i' % child.id, access_loggedin=True),
                item('Dalībnieku saraksts', 'manager:participant_list %i' % child.id, access_loggedin=True),
                item('Dalībnieku numuri', 'manager:number_list %i' % child.id, access_loggedin=True),
                item('{{ object }}', 'manager:participant %i object.id' % child.id, in_menu=False, access_loggedin=True),
            ]))
            children.append(item('Komandas', '#', url_as_pattern=False, access_loggedin=True, in_menu=False, children=[
                item('Pieteiktās komandas', 'manager:applied_team_list %i' % child.id, access_loggedin=True),
                item('{{ object }}', 'manager:edit_team %i object.id' % child.id, in_menu=False, access_loggedin=True),
                item('Dalībnieku saraksts', 'manager:team_applied_participant_list %i' % child.id, in_menu=False, access_loggedin=True),


                # item('Dalībnieku saraksts', 'manager:participant_list %i' % child.id, access_loggedin=True),
                # item('Dalībnieku numuri', 'manager:number_list %i' % child.id, access_loggedin=True),
                # item('{{ object }}', 'manager:participant %i object.id' % child.id, in_menu=False, access_loggedin=True),
            ]))
            children.append(item('Rezultāti', '#', url_as_pattern=False, in_menu=False, access_loggedin=True, children=[
                item('Saraksts', 'manager:result_list %i' % child.id, access_loggedin=True),
                item('Pievienot jaunu', 'manager:result %i' % child.id, access_loggedin=True),
                item('Atskaites', 'manager:result_reports %i' % child.id, access_loggedin=True),
            ]))
            children.append(item('Analīze', '#', url_as_pattern=False, in_menu=False, access_loggedin=True, children=[
                item('Vienādie ALIASi', 'manager:analytics_same_slug %i' % child.id, access_loggedin=True),
                item('Vairāki numuri', 'manager:analytics_multiple_numbers %i' % child.id, access_loggedin=True),
                item('Nestartē, bet numurs', 'manager:analytics_results_incorrect %i' % child.id, access_loggedin=True),
                item('Atšķiras ALIASI no numura', 'manager:analytics_different_slugs %i' % child.id, access_loggedin=True),
                item('Atšķiras ALIASI no numura2', 'manager:match_participant_number %i' % child.id, access_loggedin=True),

                item('Piesaistīt dalībnieku numuram (labot gramatiku)', 'manager:analytics_find_unmatched_participant %i' % child.id, access_loggedin=True),



            ]))
            children.append(item('Parametri', '#', url_as_pattern=False, in_menu=False, access_loggedin=True, children=[
                item('Distance admin', 'manager:distance_admin_list %i' % child.id, access_loggedin=True),
            ]))
            child_items.append(item(str(child), 'manager:competition %i' % child.id, access_loggedin=True, children=children))


        return item(str(self.competition), '#', url_as_pattern=False, children=child_items, in_menu=self.competition.is_in_menu, access_loggedin=True)

    def build_menu(self, lang):
        activate(lang)
        current_date = datetime.date.today()

        allchildren = list(self.competition.get_children().order_by('-competition_date'))

        child_items = [
            item(_("Teams"), 'competition:team %i' % self.competition.id, children=[
                item('{{ object }}', 'competition:team %i object.id' % self.competition.id, in_menu=False),
            ]),
            item(_("Standings"), 'competition:standings_list %i' % self.competition.id),
            item(_("Team Standings"), 'competition:team_standings_list %i' % self.competition.id, in_menu=False),
        ]

        # If there is still active complex payment, then we should show start list from 1st stage.
        if self.competition.complex_payment_enddate > timezone.now():
            child_items.append(item(_("Start List"), 'competition:participant_list %i' % allchildren[-1].id))

        self.build_flat_pages(self.competition, child_items, lang)

        for index, child in enumerate(allchildren, start=0):
            # if index < len(allchildren) and allchildren[index].competition_date > current_date:
            #     continue

            children = [
                item(_("Competition"), 'competition:competition %i' % child.id, in_menu=False),
                item(_("Teams"), 'competition:team %i' % child.id, children=[
                    item('{{ object }}', 'competition:team %i object.id' % child.id, in_menu=False),
                ]),
                item(_("Standings"), 'competition:standings_list %i' % child.id),
                item(_("Team Standings"), 'competition:team_standings_list %i' % child.id, in_menu=False),
                item(_("Start List"), 'competition:participant_list %i' % child.id),
                item(_("Applied Teams"), 'competition:applied_teams_list %i' % child.id),
                item(_("Maps"), 'competition:maps %i' % child.id),
            ]

            if child.competition_date <= current_date + datetime.timedelta(days=1):
                children.append(item(_("Results"), 'competition:result_distance_list %i' % child.id))
                children.append(item(_("Team Results"), 'competition:result_team_list %i' % child.id, in_menu=False))

            self.build_flat_pages(child, children, lang)

            child_items.append(item(str(child), '#', children=children))

        return item(self.competition.name, 'competition:competition %i' % self.competition.id, url_as_pattern=True, children=child_items, in_menu=self.competition.is_in_menu)

    def get_startlist_table_class(self, distance=None):
        return ParticipantTable


    def recalculate_team_results(self):
        """
        Function to recalculate all team results for current competition.
        """
        teams = Team.objects.filter(member__memberapplication__competition=self.competition, member__memberapplication__kind=MemberApplication.KIND_PARTICIPANT).order_by('id').distinct('id')
        for team in teams:
            print(team.id)
            self.recalculate_team_result(team=team)

    def recalculate_team_result(self, team_id=None, team=None):
        """
        Function to recalculate team's result for current competition.
        After current competition point recalculation, standing total points are recalculated as well.
        """
        if not team and not team_id:
            raise Exception('Team or Team Id must be set')
        if not team:
            team = Team.objects.get(id=team_id)
        else:
            team_id = team.id

        team_member_results = Team.objects.filter(
            id=team_id,
            member__memberapplication__competition=self.competition,
            member__memberapplication__kind=MemberApplication.KIND_PARTICIPANT,
            member__memberapplication__participant__result__competition=self.competition).order_by('-member__memberapplication__participant__result__points_distance').values_list('member__memberapplication__participant__result__points_distance')[:4]
        standing, created = TeamResultStandings.objects.get_or_create(team_id=team_id)

        # Set current competition points to best 4 riders sum
        setattr(standing, 'points%i' % self.competition_index, sum([val[0] for val in team_member_results if val[0]]))

        # Recalculate total sum.
        point_list = [standing.points1, standing.points2, standing.points3, standing.points4, standing.points5, standing.points6, standing.points7, standing.points8]

        # if team.distance_id == self.SPORTA_DISTANCE_ID:
        #     point_list.pop(3)  # 4.stage is not taken because it is UCI category

        point_list = filter(None, point_list)  # remove None from list

        # Get 7 best results for men teams and 5 best results for women teams.
        if Team.objects.get(id=team_id).is_w:
            point_list = sorted(point_list, reverse=True)[:5]
        else:
            point_list = sorted(point_list, reverse=True)[:7]


        setattr(standing, 'points_total', sum(point_list))

        standing.save()

        # Log information about calculated values
        # Log.objects.create(content_object=team, action="Recalculated team standing", params={
        #     'points_total': standing.points_total,
        #     'points%i' % self.competition_index: getattr(standing, 'points%i' % self.competition_index)
        # })

        return standing

    def _participant_standings_points(self, standing, distance=False):
        """
        This is private function that calculates points for participant based on distance.
        """
        stages = range(1, self.STAGES_COUNT+1)

        #if standing.distance_id == self.SPORTA_DISTANCE_ID:
        #    stages.remove(4)  # 4.stage is not taken because it is UCI category
        if distance:
            points = sorted((getattr(standing, 'distance_points%i' % stage) for stage in stages), reverse=True)
        else:
            points = sorted((getattr(standing, 'group_points%i' % stage) for stage in stages), reverse=True)

        return sum(points[0:self.CALCULATE_STANDING_FROM_STAGES])

    def _participant_event_place_sum(self, standing):
        """
        This is private function that calculates sum from best 5 places in singles events
        """
        stages = range(1, self.STAGES_COUNT+1)
        places = sorted(filter(None, (getattr(standing, "distance_place%i" % stage) for stage in stages)))

        return sum(places[0:self.CALCULATE_STANDING_FROM_STAGES])

    def recalculate_standing_points(self, standing):
        """
        This function recalculates distance and group total points for provided standing.
        If standing is children distance, then distance total is not calculated.
        """
        if standing.distance_id != self.BERNU_DISTANCE_ID:  # Children competition doesn't have distance_total
            standing.distance_total = self._participant_standings_points(standing, distance=True)
            standing.distance_place_sum = self._participant_event_place_sum(standing)

        standing.group_total = self._participant_standings_points(standing)

    def recalculate_standing_for_result(self, result):
        """
        This function received result in input and assigned result to standing object.
        Afterwards function calls recalculate_standing_points to recalculate points for standing
        """
        if not result.standings_object:
            print(result.id)
            standing, created = SebStandings.objects.get_or_create(competition=result.competition.parent, participant_slug=result.participant.slug, distance=result.participant.distance, defaults={'participant': result.participant})
            result.standings_object = standing
            result.save()
        else:
            standing = result.standings_object
        standing.set_points()
        self.recalculate_standing_points(standing)
        standing.save()

        if result.participant.team:
            self.recalculate_team_result(team=result.participant.team)


    def recalculate_standing_for_results(self):
        """
        Function iterates through all results and recalculates standings.
        """
        all_results = Result.objects.filter(competition=self.competition)
        for result in all_results:
            self.recalculate_standing_for_result(result)

        self.assign_standing_places()  # Reassign places

    def assign_standing_places(self):
        """
        Function iterates through all standings and assign place based on total points, total seconds and points in last stage
        """
        cursor = connection.cursor()

        # TODO: Exclude children from assigning distance place

        # First assign distance place
        cursor.execute("""
        UPDATE
            results_sebstandings r
        SET
            distance_place = res2.distance_row_nr,
            group_place = res2.group_row_nr
        FROM
        (
        Select res.id, res.competition_id, res.distance_id, res.group_total, res.distance_total,
        row_number() OVER (PARTITION BY res.competition_id, res.distance_id ORDER BY
        res.distance_id, res.distance_total desc, res.distance_place_sum ASC,
        res.distance_points7 desc, res.distance_points6 desc, res.distance_points5 desc, res.distance_points4 desc,
        res.distance_points3 desc, res.distance_points2 desc, res.distance_points1 desc, r.time ASC) as distance_row_nr,
        row_number() OVER (PARTITION BY res.competition_id, res.distance_id, p.group ORDER BY
        res.distance_id, p.group, res.group_total desc, res.distance_place_sum ASC, res.group_points7 desc, res.group_points6 desc, res.group_points5 desc,
        res.group_points4 desc, res.group_points3 desc, res.group_points2 desc, res.group_points1 desc, r.time ASC
        ) as group_row_nr
        FROM results_sebstandings As res
        INNER JOIN registration_participant p ON res.participant_id = p.id
        INNER JOIN (Select DISTINCT ON (standings_object_id) * from results_result order by standings_object_id ) r on r.standings_object_id = res.id
        ) res2
        WHERE res2.competition_id = %s AND r.id = res2.id
        """, [self.competition.parent.id, ])


    def recalculate_all_standings(self):
        """
        ===== MAIN FUNCTION =====
        Function recalculates all standings for current competition. Function recalculates team results also.
        """
        if self.competition.level == 2:  # if class is called with stage competition, then recalculate all results
            self.recalculate_standing_for_results()
            self.recalculate_team_results()  # Recalculate team total points for current competition
        else:
            pass  # TODO: Create team point recalculation for all stages at the same time


    def process_chip_create_participant(self, chip):
        participant = Participant.objects.filter(slug=chip.nr.participant_slug, competition_id__in=chip.competition.get_ids() if not chip.competition.is_individual else (chip.competition_id, ), distance=chip.nr.distance, is_participating=True)
        if not participant:
            participant_data = Participant.objects.filter(slug=chip.nr.participant_slug, competition_id__in=chip.competition.get_all_children_ids(), distance=chip.nr.distance, is_participating=True).order_by('-competition__id')
            if participant_data:
                participant_data = participant_data.values()[0]
                # TODO: Refresh list
                for pop_element in ['id', 'application_id', 'comment', 'created', 'created_by_id', 'insurance_id', 'legacy_id', 'modified', 'modified_by_id', 'price_id', 'registrant_id', 'is_sent_number_sms', 'is_sent_number_email', 'registration_dt']:
                    participant_data.pop(pop_element)

                participant_data.update({'is_temporary': True, 'competition_id': chip.competition.id, })

                participant = [Participant.objects.create(**participant_data), ]
                Log.objects.create(content_object=participant[0], action="Chip process", message="Participant was not found, so created temporary one based on previous stage data.")
                print('Created participant with ID %i' % participant[0].id)
            else:
                return False
        return participant

    def process_chip_result(self, chip_id, sendsms=True, recalc=False):
        """
        Function processes chip result and recalculates all standings
        """
        chip = ChipScan.objects.get(id=chip_id)

        Log.objects.create(content_object=chip, action="Chip process", message="Started")

        result_time, seconds = self.calculate_time(chip)

        # Do not process if finished in 10 minutes.
        if seconds < 10 * 60: # 10 minutes
            Log.objects.create(content_object=chip, action="Chip process", message="Chip result less than 10 minutes. Ignoring.")
            return None

        if chip.is_processed:
            Log.objects.create(content_object=chip, action="Chip process", message="Chip already processed")
            return None

        results = Result.objects.filter(competition=chip.competition, number=chip.nr).exclude(time=None)
        if results and not recalc:
            Log.objects.create(content_object=chip, action="Chip process", message="Chip ignored. Already have result")
        else:
            participant = self.process_chip_create_participant(chip)
            if participant:
                result, created = Result.objects.get_or_create(competition=chip.competition, participant=participant[0], number=chip.nr, )
                result.time = result_time
                result.set_all()
                result.save()

                # Update standings... Asynchronously
                recalculate_standing_for_result.delay(self.competition_id, result.id)

                # To send out SMS we need place set.
                self.assign_result_place()

                if sendsms:
                    create_result_sms(result.id)

            else:
                Log.objects.create(content_object=chip, action="Chip error", message="Participant not found")

        print(chip)


    def recalculate_all_points(self):
        """
        MAIN FUNCTION FROM MANAGER
        This function is called from manager view to manually recalculate points.
        This function is called in case there are errors in given points.
        """
        distances = [self.SPORTA_DISTANCE_ID, self.TAUTAS_DISTANCE_ID, self.VESELIBAS_DISTANCE_ID]

        if hasattr(self, "VESELIBAS_DISTANCE2_ID"):
            distances.append(self.VESELIBAS_DISTANCE2_ID)

        recalculate_places = False
        results = Result.objects.filter(competition=self.competition, participant__distance_id__in=distances).exclude(time=None)
        for result in results:
            print(result.id)
            if result.set_all():
                recalculate_places = True
                result.save()
            self.recalculate_standing_for_result(result)

        if recalculate_places:
            self.assign_standing_places()
            self.recalculate_team_results()

        self.assign_result_place()

    def calculate_points_distance(self, result, top_result=None):
        """
        Function used to calculate distance points
        """
        if result.number.distance_id == self.BERNU_DISTANCE_ID:
            return result.points_distance  # For children lets return the same number.

        total = 1000
        if result.number.distance_id == self.TAUTAS_DISTANCE_ID:
            total = 900

        if result.status:  # If result has the status then that means that result is 0
            return 0

        if not top_result:
            try:
                top_result = Result.objects.filter(competition=result.competition, number__distance=result.number.distance, status='').exclude(time=None).order_by('time')[0]
            except IndexError:
                return total

        if result.time is None:
            return 0

        return math.trunc((float(math.trunc(time_to_seconds(top_result.time))) / float(math.trunc(time_to_seconds(result.time)))) * total)

    def calculate_points_group(self, result):
        """
        Function used to recalculate group points
        """
        if result.number.distance_id == self.BERNU_DISTANCE_ID:
            return result.points_group  # For children lets return the same number.

        total = 1000
        if result.number.distance_id == self.TAUTAS_DISTANCE_ID:
            total = 900

        if result.status:
            return 0

        try:
            top_result = Result.objects.filter(competition=result.competition, number__distance=result.number.distance, participant__group=result.participant.group, status='').exclude(time=None).order_by('time')[0]
        except IndexError:
            return total

        if result.time is None:
            return 0

        return math.trunc((float(math.trunc(time_to_seconds(top_result.time))) / float(math.trunc(time_to_seconds(result.time)))) * total)

    def get_result_table_class(self, distance, group=None):
        if distance.id == self.BERNU_DISTANCE_ID:  # children distance
            return ResultChildrenGroupTable
        if group:
            return ResultGroupTable
        else:
            return ResultDistanceTable

    def get_standing_table_class(self, distance, group=None):
        if distance.id == self.BERNU_DISTANCE_ID:  # children distance
            return ResultChildrenGroupStandingTable
        if group:
            return ResultGroupStandingTable
        else:
            return ResultDistanceStandingTable




    def assign_result_place(self):
        """
        Assign result place based on result time. Optimized to use raw SQL.
        """
        cursor = connection.cursor()

        # First assign distance place
        cursor.execute("""
UPDATE
    results_result r
SET
    result_distance = res2.distance_row_nr,
    result_group = res2.group_row_nr
FROM
(
Select res.id, result_distance, res.competition_id, res.time, p.is_competing, p.distance_id,
row_number() OVER (PARTITION BY res.competition_id, nr.distance_id ORDER BY nr.distance_id, res.status asc, res.time, res.id) as distance_row_nr,
row_number() OVER (PARTITION BY res.competition_id, nr.distance_id, p.group ORDER BY nr.distance_id, p.group, res.status asc, res.time, res.id) as group_row_nr
FROM results_result As res
INNER JOIN registration_number nr ON res.number_id = nr.id
INNER JOIN registration_participant p ON res.participant_id = p.id
WHERE p.is_competing is true and res.time IS NOT NULL
) res2
WHERE res2.competition_id = %s and res2.distance_id <> %s and res2.time IS NOT NULL and res2.is_competing is true
AND r.id = res2.id
""", [self.competition_id, self.BERNU_DISTANCE_ID])
        # Then unset places to others
        cursor.execute("""
UPDATE
    results_result r
SET
    result_distance = NULL,
    result_group = NULL
FROM
(
Select res.id, result_distance, res.competition_id, res.time, p.is_competing, p.distance_id
FROM results_result As res
INNER JOIN registration_number nr ON res.number_id = nr.id
INNER JOIN registration_participant p ON res.participant_id = p.id
) res2
WHERE res2.competition_id = %s and res2.distance_id <> %s and (res2.time IS NULL or res2.is_competing is false)
AND r.id = res2.id
""", [self.competition_id, self.BERNU_DISTANCE_ID])


    def assign_passage(self, reset=False):
        if self.competition.level != 2:
            raise Exception('We allow assigning passages only for stages.')

        if reset:
            HelperResults.objects.filter(competition=self.competition).update(passage_assigned=None)

        update_helper_result_table(self.competition_id, update=True)

        for distance_id in (self.SPORTA_DISTANCE_ID, self.TAUTAS_DISTANCE_ID):
            helperresults = HelperResults.objects.filter(competition=self.competition, participant__distance_id=distance_id, participant__is_participating=True, passage_assigned=None).select_related('participant').order_by('-calculated_total', 'participant__registration_dt')
            for passage_nr, total, passage_extra in self.passages.get(distance_id):
                specials = [obj.participant_slug for obj in PreNumberAssign.objects.filter(competition=self.competition, distance_id=distance_id).filter(segment=passage_nr)]
                # Assign passage for specials
                HelperResults.objects.filter(competition=self.competition, participant__distance_id=distance_id, participant__is_participating=True, participant__slug__in=specials, passage_assigned=None).update(passage_assigned=passage_nr)

                places = total - passage_extra

                for result in helperresults[0:places]:
                    result.passage_assigned = passage_nr
                    result.save()

                # Exceptions

                # In 1.stage 10 girls and 50 women that are not in first 3 passages will be assigned to 4.passage
                if passage_nr == 4 and self.competition_index == 1 and distance_id == self.TAUTAS_DISTANCE_ID:
                    girls = HelperResults.objects.filter(competition=self.competition, participant__distance_id=distance_id, participant__is_participating=True, passage_assigned=None, participant__group__in=('W-16', 'T W-18')).order_by('-calculated_total', 'participant__registration_dt')[0:10]
                    for _ in girls:
                        _.passage_assigned = passage_nr
                        _.save()
                    women = HelperResults.objects.filter(competition=self.competition, participant__distance_id=distance_id, participant__is_participating=True, passage_assigned=None, participant__group__in=('T W', 'T W-35', 'T W-45')).order_by('-calculated_total', 'participant__registration_dt')[0:50]
                    for _ in women:
                        _.passage_assigned = passage_nr
                        _.save()


    def assign_numbers_continuously(self):
        self.assign_numbers(reassign=False, assign_special=False)


    def assign_numbers(self, reassign=False, assign_special=False, assign_children=True):

        # Update helper results before assigning
        update_helper_result_table(self.competition_id, update=True)

        if self.competition.level != 2:
            raise Exception('We allow assigning numbers only for stages.')

        if reassign:
            Number.objects.filter(competition_id__in=self.competition.get_ids()).update(participant_slug='', number_text='')
            Participant.objects.filter(competition_id__in=self.competition.get_ids(), is_participating=True).update(primary_number=None)

        parent_competition = self.competition.parent

        if assign_special:
            # first assign special numbers
            pre_numbers = PreNumberAssign.objects.filter(competition_id__in=self.competition.get_ids()).exclude(number=None)
            for nr in pre_numbers:
                number = Number.objects.get(number=nr.number, competition=parent_competition, distance=nr.distance)
                print("%s - %s" % (number, nr.participant_slug))
                number.participant_slug = nr.participant_slug
                number.save()
                Participant.objects.filter(competition_id__in=self.competition.get_ids(), is_participating=True, distance=number.distance, slug=number.participant_slug).update(primary_number=number)


        helperresults = HelperResults.objects.filter(competition=self.competition, participant__is_participating=True, participant__primary_number=None).select_related('participant').order_by('participant__distance', '-calculated_total', 'participant__registration_dt')

        for result in helperresults:
            participant = result.participant

            if not assign_children and participant.distance_id == self.BERNU_DISTANCE_ID:
                continue
            #
            # if participant.distance_id == self.VESELIBAS_DISTANCE_ID:
            #     if participant.birthday.year not in (2003, 2004, 2005):
            #         next_number = Number.objects.filter(participant_slug='', distance=participant.distance, number__lt=5001).order_by('number')[0]
            #     else:
            #         next_number = Number.objects.filter(participant_slug='', distance=participant.distance, number__gte=5001).order_by('number')[0]

            group = self.get_group_for_number_search(participant.distance_id, participant.gender, participant.birthday)
            try:
                number = Number.objects.get(participant_slug=participant.slug, distance=participant.distance, group=group)
                if not participant.primary_number:
                    participant.primary_number = number
                    participant.save()
            except:
                next_number = Number.objects.filter(participant_slug='', distance=participant.distance, group=group).order_by('number')[0]
                next_number.participant_slug = participant.slug
                next_number.number_text = str(participant.registration_dt)
                print("%s - %s" % (next_number, participant.slug))
                next_number.save()
                participant.primary_number = next_number
                participant.save()

    def get_group_for_number_search(self, distance_id, gender, birthday, group=None):
        if not isinstance(birthday, datetime.date) and birthday:
            try:
                birthday = datetime.datetime.strptime(birthday, "%Y-%m-%d").date()
            except:
                return 'error-no-group'

        if distance_id in (self.SPORTA_DISTANCE_ID, self.TAUTAS_DISTANCE_ID, self.VESELIBAS_DISTANCE_ID):
            return ''
        elif distance_id == self.BERNU_DISTANCE_ID:
            try:
                return self.assign_group(distance_id, gender, birthday)
            except:
                return 'error-no-group'



    def import_children_csv(self, filename): # berni1p14.xls

        result_column = 7 + (self.competition_index * 3)

        with open(filename, 'rb') as csvfile:
            results = csv.reader(csvfile)
            results.next()  # header line
            for row in results:
                assign_number = False

                slug = slugify("%s-%s-%s" % (row[2].decode('utf-8'), row[3].decode('utf-8'), row[4].decode('utf-8')))
                print(row)
                participant = Participant.objects.filter(slug=slug, competition_id__in=self.competition.get_ids(), is_participating=True, distance_id=self.BERNU_DISTANCE_ID)
                if participant:
                    participant = participant.get()
                else:
                    data = {
                        'competition_id': self.competition_id,
                        'distance_id': self.BERNU_DISTANCE_ID,
                        'team_name': row[6].decode('utf-8'),
                        'is_participating': True,
                        'first_name': row[2].decode('utf-8'),
                        'last_name': row[3].decode('utf-8'),
                        'birthday': datetime.date(int(row[4]), 1, 1),
                        'is_only_year': True,
                        'phone_number': row[8],
                        'gender': '',
                    }
                    if row[7]:
                        try:
                            data.update({'bike_brand2': row[7], })
                        except:
                            pass

                    if row[5] == 'B 05-04 M':
                        data.update({'gender': 'W'})
                    elif row[5] == 'B 05-04 Z':
                        data.update({'gender': 'M'})

                    participant = Participant.objects.create(**data)

                number_group = participant.group
                if number_group in ('B 05-04 M', 'B 05-04 Z'):
                    number_group = 'B 05-04'
                # Assign number
                number = Number.objects.filter(competition=self.competition.parent, distance_id=self.BERNU_DISTANCE_ID, number=row[1], group=number_group).order_by('-id')
                number.update(participant_slug=participant.slug)
                if number:
                    participant.primary_number = number.get()
                    participant.save()

                if row[result_column]:
                    result, created = Result.objects.get_or_create(competition=self.competition, participant=participant, number=number.get(), result_group=row[result_column] if row[result_column] else None, points_group=row[result_column+1] if row[result_column+1] else 0, status=row[result_column-1])
                    self.recalculate_standing_for_result(result)
                else:
                    print('didnt participate')
        self.assign_standing_places() class Application(TimestampMixin, models.Model):
    PAY_STATUS = XChoices((0, 'not_payed', _("Haven't Payed")),
                          (10, 'waiting', _('Waiting for Payment')),
                          (20, 'payed', _('Payed')),
                          (30, 'never', _('Won\'t pay')),
                          (-10, 'cancelled', _('Cancelled')), )

    competition = models.ForeignKey('core.Competition', verbose_name=_('Competition'))
    payment_status = models.SmallIntegerField(_('Payment Status'), default=PAY_STATUS.not_payed, choices=PAY_STATUS)
    discount_code = models.ForeignKey('payment.DiscountCode', blank=True, null=True)

    email = models.EmailField(blank=True)
    can_send_newsletters = models.BooleanField(_("I want to receive velo.lv news in email."), default=True)

    code = models.CharField(max_length=50, default=uuid.uuid4, unique=True)
    legacy_id = models.IntegerField(blank=True, null=True)

    company_name = models.CharField(_('Company name / Full Name'), max_length=100, blank=True)
    company_vat = models.CharField(_('VAT Number'), max_length=100, blank=True)
    company_regnr = models.CharField(_('Company number / SSN'), max_length=100, blank=True)
    company_address = models.CharField(_('Address'), max_length=100, blank=True)
    company_juridical_address = models.CharField(_('Juridical Address'), max_length=100, blank=True)
    invoice_show_names = models.BooleanField(_('Show participant names in invoice'), default=True)

    external_invoice_code = models.CharField(_('Invoice code'), max_length=100, blank=True)  # invoice code from e-rekins used to allow downloading invoice from velo.lv
    external_invoice_nr = models.CharField(_('Invoice Number'), max_length=20, blank=True)  # invoice number from e-rekins used in card payment
    invoice = models.ForeignKey('payment.Invoice', null=True, blank=True)

    donation = models.DecimalField(_('Donation'), max_digits=20, decimal_places=2, default=0.0)

    # Totals
    total_discount = models.DecimalField(max_digits=20, decimal_places=2, default=0.0)
    total_entry_fee = models.DecimalField(max_digits=20, decimal_places=2, default=0.0)
    total_insurance_fee = models.DecimalField(max_digits=20, decimal_places=2, default=0.0)
    final_price = models.DecimalField(max_digits=20, decimal_places=2, default=0.0)

    params = JSONField(default={})

    language = models.CharField(max_length=10, choices=LANGUAGES, default='lv')

    payment_set = GenericRelation(Payment)

    def save(self, *args, **kwargs):
        self.set_final_price()
        return super(Application, self).save(*args, **kwargs)

    def set_final_price(self):
        if self.discount_code:
            _class = load_class(self.discount_code.campaign.discount_kind)
            discount = _class(application=self)
            price = discount.get_final_price_for_application()
            if isinstance(price, float):
                self.final_price = price + float(self.donation)
                return
            else:
                self.discount_code = None

        self.final_price = float(self.total_entry_fee) + float(self.total_insurance_fee) + float(self.donation)

    @property
    def competition_name(self):
        if self.competition.level == 2:
            return '%s - %s' % (self.competition.parent, self.competition)
        else:
            return str(self.competition) class ChangedName(models.Model):
    slug = models.SlugField()
    new_slug = models.SlugField()

    def __str__(self):
        return self.new_slug class Number(StatusMixin, TimestampMixin, models.Model):
    competition = models.ForeignKey('core.Competition')
    distance = models.ForeignKey('core.Distance')
    number = models.IntegerField()
    number_text = models.CharField(max_length=50, blank=True)
    participant_slug = models.SlugField(blank=True)
    group = models.CharField(_('Group'), max_length=50, blank=True)

    def __str__(self):
        if not self.group:
            return str(self.number)
        else:
            if self.number > 1000:
                return '%s - %i' % (self.group, int(str(self.number)[1:]))
            else:
                return '%s - %i' % (self.group, self.number)

    class Meta:
        unique_together = ('competition', 'number', 'group')
        ordering = ('distance', 'group', 'number', ) class Participant(TimestampMixin, models.Model):
    GENDER_CHOICES = (
        ('M', _('Male')),
        ('F', _('Female')),
    )
    TSHIRT_SIZE_CHOICES = (
        ('XXS', _('UNISEX XXS')), ('XS', _('UNISEX XS')), ('S', _('UNISEX S')), ('M', _('UNISEX M')),
        ('L', _('UNISEX L')), ('XL', _('UNISEX XL')), ('XXL', _('UNISEX XXL')), ('XXL', _('UNISEX XXL'))
    )
    application = models.ForeignKey(Application, blank=True, null=True)
    competition = models.ForeignKey('core.Competition')
    distance = models.ForeignKey('core.Distance', blank=True, null=True)
    price = models.ForeignKey('payment.Price', blank=True, null=True)

    code_short = models.CharField(max_length=20, blank=True, db_index=True)

    company_participant = models.ForeignKey('registration.CompanyParticipant', blank=True, null=True)

    discount_amount = models.DecimalField(max_digits=20, decimal_places=2, default=0.0)

    insurance = models.ForeignKey('core.Insurance', blank=True, null=True)

    team = models.ForeignKey('team.Team', blank=True, null=True)  # If participant is in official team, then this is filled
    team_name = models.CharField(_('Team'), max_length=50, blank=True)  # If participant is not within official team, then he can fill this field
    team_name_slug = models.SlugField(blank=True)

    is_participating = models.BooleanField(_('Is Participating'), default=False)
    is_paying = models.BooleanField(_('Is Paying'), default=True)
    is_competing = models.BooleanField(_('Is Competing'), default=True)  # in case this cyclist is participating for fun, then is_competing should be set to false.
    is_shown_public = models.BooleanField(_('Is Shown Publicly'), default=True)  # if participant doesn't want to be seen publicly in website, this should be set to false.

    first_name = models.CharField(_('First Name'), max_length=60, blank=True)
    last_name = models.CharField(_('Last Name'), max_length=60, blank=True)
    birthday = models.DateField(_('Birthday'), blank=True, null=True)
    is_only_year = models.BooleanField(default=False)
    slug = models.SlugField(blank=True)

    gender = models.CharField(_('Gender'), max_length=1, choices=GENDER_CHOICES, blank=True)

    ssn = models.CharField(_('Social Security Number'), max_length=12, blank=True)
    ssn_hashed = models.CharField(_('Social Security Number Hashed'), max_length=44, blank=True)

    phone_number = models.CharField(_('Phone Number'), max_length=60, blank=True, help_text=_("Result will be sent to this phone number"))
    email = models.EmailField(_('Email'), blank=True)

    send_email = models.BooleanField(_('Send Email'), default=True)
    send_sms = models.BooleanField(_('Send SMS'), default=True)

    country = CountryField(_('Country'), blank=True, null=True)
    city = models.ForeignKey('core.Choices', related_name='+', limit_choices_to={'kind': Choices.KINDS.city}, verbose_name=_('City'), blank=True, null=True)

    bike_brand = models.ForeignKey('core.Choices', related_name='+', limit_choices_to={'kind': Choices.KINDS.bike_brand}, verbose_name=_('Bike Brand'), blank=True, null=True)
    bike_brand2 = models.CharField(_('Bike Brand'), max_length=20, blank=True, help_text=_("Other bike brand"))

    occupation = models.ForeignKey('core.Choices', related_name='+', limit_choices_to={'kind': Choices.KINDS.occupation}, verbose_name=_('Occupation'), blank=True, null=True)
    where_heard = models.ForeignKey('core.Choices', related_name='+', limit_choices_to={'kind': Choices.KINDS.heard}, verbose_name=_('Where Heard'), blank=True, null=True)

    group = models.CharField(_('Group'), max_length=50, blank=True, help_text=_('Assigned automatically'))  # moved group away from results

    registrant = models.ForeignKey('core.User', blank=True, null=True, verbose_name=_('Registrant'))
    legacy_id = models.IntegerField(blank=True, null=True)

    full_name = models.CharField(_('Full Name'), max_length=120, blank=True)

    primary_number = models.ForeignKey('registration.Number', blank=True, null=True)

    is_temporary = models.BooleanField(default=False)
    is_sent_number_email = models.BooleanField(default=False)
    is_sent_number_sms = models.BooleanField(default=False)

    registration_dt = models.DateTimeField(_("Registration Date"), blank=True, null=True)

    comment = models.TextField(blank=True)

    total_entry_fee = models.DecimalField(max_digits=20, decimal_places=2, default=0.0)
    total_insurance_fee = models.DecimalField(max_digits=20, decimal_places=2, default=0.0)
    final_price = models.DecimalField(max_digits=20, decimal_places=2, default=0.0)

    survey_answer1 = models.CharField(max_length=20, blank=True)

    t_shirt_size = models.CharField(_('T-Shirt size'), max_length=3, choices=TSHIRT_SIZE_CHOICES, blank=False, null=True)

    _competition_class = None

    class Meta:
        ordering = ('distance', 'created')
        verbose_name = _('participant')
        verbose_name_plural = _('participants')

    def __str__(self):
        return '%s %s - %s %s' % (self.first_name, self.last_name, self.competition, self.distance)

    def get_competition_class(self):
        if not self._competition_class:
            class_ = load_class(self.competition.processing_class)
            self._competition_class = class_(self.competition.id)
        return self._competition_class

    def set_slug(self):
        if self.birthday:
            try:
                self.slug = CustomSlug.objects.get(first_name=self.first_name, last_name=self.last_name, birthday=self.birthday).slug
            except CustomSlug.DoesNotExist:
                self.slug = slugify('%s-%s-%i' % (self.first_name, self.last_name, self.birthday.year), only_ascii=True)
        else:
            self.slug = slugify('%s-%s' % (self.first_name, self.last_name), only_ascii=True)

    def set_group(self):
        if not self.group and self.is_participating:
            self.group = self.get_competition_class().assign_group(self.distance_id, self.gender, self.birthday, participant=self)

    def save(self, *args, **kwargs):
        prev_participant = None
        if self.pk:
            prev_participant = Participant.objects.get(id=self.pk)

        self.full_name = '%s %s' % (self.first_name, self.last_name)  # Full name always should be based on first_name and last_name fields

        self.team_name_slug = slugify(self.team_name.replace(' ', ''))

        # In case first name, last name or birthday is changed, then slug changes as well.
        old_slug = self.slug
        self.set_slug()
        if old_slug != self.slug and self.is_participating:
            Log.objects.create(content_object=self, action="Changed SLUG", message="Changing on number model", params={'old_slug':old_slug, 'new_slug': self.slug})
            self.numbers(slug=old_slug).update(participant_slug=self.slug)  # We update number slugs to match new slug

            if self.team:
                team = self.team
                self.team = None  # Remove connection to existing team member.

                # Because of slug change connection to team is disconnected.
                # After disconnection team points are recalculated on every stage team have participated.
                try:
                    member = team.member_set.get(slug=old_slug)
                    for appl in member.memberapplication_set.all():
                        class_ = load_class(appl.competition.processing_class)
                        processing_class = class_(competition=appl.competition)
                        processing_class.recalculate_team_result(team=team)
                        appl.participant = None
                        appl.participant_unpaid = None
                        appl.participant_potential = None
                        appl.save()
                except:
                    pass

            sebs = self.primary_sebstandings_set.filter(competition_id__in=self.competition.get_ids())
            if sebs:
                sebs.update(participant_slug=self.slug)
                # Check if there are double standings that should be merged
                from velo.results.tasks import merge_standings
                merge_standings.delay(self.competition.parent_id if self.competition.level == 2 else self.competition_id)

        self.set_group()

        if not self.primary_number and self.is_participating:
            numbers = self.numbers().order_by('-number')
            if numbers:
                self.primary_number = numbers[0]

        if not self.registration_dt:
            self.registration_dt = timezone.now()

        # Recalculate totals. # TODO: This should be done when creating payment, not on any save.
        if self.application:
            if self.application.payment_status == Application.PAY_STATUS.not_payed or not self.application.discount_code:
                recalculate_participant(self, commit=False)

        obj = super(Participant, self).save(*args, **kwargs)

        if not self.code_short and self.id:
            hashids = Hashids(salt=settings.SECRET_KEY, min_length=7, alphabet=settings.HASH_ALPHABET)
            self.code_short = hashids.encode(self.id)
            self.save()

        if self.is_participating and (not prev_participant or not prev_participant.is_participating):
            from velo.results.tasks import master_update_helper_result_table
            master_update_helper_result_table.delay(participant_id=self.id)

        if old_slug != self.slug and self.is_participating:
            from velo.team.utils import match_participant_to_applied
            match_participant_to_applied(self)

        return obj

    def numbers(self, slug=None):
        self.set_group()
        if not slug:
            slug = self.slug
        group = self.get_competition_class().get_group_for_number_search(self.distance_id, self.gender, self.birthday)
        number_queryset = Number.objects.filter(participant_slug=slug, competition_id__in=self.competition.get_ids(), distance=self.distance, group=group)

        return number_queryset class UCICategory(models.Model):
    category = models.CharField(max_length=60, blank=True)
    group = models.CharField(max_length=60, blank=True)
    first_name = models.CharField(_('First Name'), max_length=60, blank=True)
    last_name = models.CharField(_('Last Name'), max_length=60, blank=True)
    issued = models.DateField(blank=True, null=True)
    code = models.CharField(max_length=20)
    birthday = models.DateField(_('Birthday'), help_text=_('YYYY-MM-DD'), blank=True, null=True)
    slug = models.SlugField(blank=True, null=True)
    valid_until = models.DateField(blank=True, null=True)

    def save(self, *args, **kwargs):
        if self.birthday:
            self.slug = slugify('%s-%s-%i' % (self.first_name, self.last_name, self.birthday.year), only_ascii=True)
        else:
            self.slug = slugify('%s-%s' % (self.first_name, self.last_name), only_ascii=True)
        return super().save(*args, **kwargs) class PreNumberAssign(models.Model):
    competition = models.ForeignKey('core.Competition')
    distance = models.ForeignKey('core.Distance')
    number = models.IntegerField(blank=True, null=True, help_text=_('If specific number should be given'))
    segment = models.IntegerField(blank=True, null=True, help_text=_('If specific passage should be given'))
    participant_slug = models.SlugField(blank=True)
    group_together = models.SlugField(blank=True, null=True, help_text=_('Group together in one passage'))

    description = models.TextField(blank=True) class SebStandings(models.Model):
    competition = models.ForeignKey('core.Competition')
    distance = models.ForeignKey('core.Distance')

    participant_slug = models.SlugField(blank=True)
    participant = models.ForeignKey('registration.Participant', related_name='primary_%(class)s_set', )  # Should be automatically updated to first participant in competition series.

    group_points1 = models.IntegerField('1.', blank=True, null=True)
    group_points2 = models.IntegerField('2.', blank=True, null=True)
    group_points3 = models.IntegerField('3.', blank=True, null=True)
    group_points4 = models.IntegerField('4.', blank=True, null=True)
    group_points5 = models.IntegerField('5.', blank=True, null=True)
    group_points6 = models.IntegerField('6.', blank=True, null=True)
    group_points7 = models.IntegerField('7.', blank=True, null=True)
    group_points8 = models.IntegerField('8.', blank=True, null=True)

    group_total = models.IntegerField(blank=True, null=True)
    group_place = models.IntegerField(blank=True, null=True)

    distance_points1 = models.IntegerField('1.', blank=True, null=True)
    distance_points2 = models.IntegerField('2.', blank=True, null=True)
    distance_points3 = models.IntegerField('3.', blank=True, null=True)
    distance_points4 = models.IntegerField('4.', blank=True, null=True)
    distance_points5 = models.IntegerField('5.', blank=True, null=True)
    distance_points6 = models.IntegerField('6.', blank=True, null=True)
    distance_points7 = models.IntegerField('7.', blank=True, null=True)
    distance_points8 = models.IntegerField('8.', blank=True, null=True)

    distance_place1 = models.IntegerField('1.', blank=True, null=True)
    distance_place2 = models.IntegerField('2.', blank=True, null=True)
    distance_place3 = models.IntegerField('3.', blank=True, null=True)
    distance_place4 = models.IntegerField('4.', blank=True, null=True)
    distance_place5 = models.IntegerField('5.', blank=True, null=True)
    distance_place6 = models.IntegerField('6.', blank=True, null=True)
    distance_place7 = models.IntegerField('7.', blank=True, null=True)
    distance_place8 = models.IntegerField('8.', blank=True, null=True)

    distance_total = models.IntegerField(blank=True, null=True)
    distance_total_seconds = models.FloatField(blank=True, null=True)
    distance_place = models.IntegerField(blank=True, null=True)

    distance_place_sum = models.IntegerField(blank=True, null=True)

    @property
    def results(self):
        ctype = ContentType.objects.get_for_model(self.__class__)
        return Result.objects.filter(standings_content_type__pk=ctype.id, standings_object_id=self.id)

    @property
    def stages_participated(self):
        stages = [1, 2, 3, 4, 5, 6, 7, 8]
        stages_participated = 0
        for stage in stages:
            if getattr(self, "distance_points%i" % stage, 0) and getattr(self, "distance_points%i" % stage, 0) > 0:
                stages_participated += 1
        return stages_participated

    def set_points(self):
        stages = [1, 2, 3, 4, 5, 6, 7, 8]
        mapping = {obj.id: index for index, obj in enumerate(self.competition.get_children(), start=1)}

        results = self.results

        for result in results:
            setattr(self, "group_points%i" % mapping.get(result.competition_id), result.points_group)
            setattr(self, "distance_points%i" % mapping.get(result.competition_id), result.points_distance)
            setattr(self, "distance_place%i" % mapping.get(result.competition_id), result.result_distance)
            try:
                stages.remove(mapping.get(result.competition_id))
            except:
                Log.objects.create(content_object=self, message="Multiple results in stage %s" % result.competition)
        for stage in stages:
            setattr(self, "group_points%i" % stage, 0)
            setattr(self, "distance_points%i" % stage, 0)
            setattr(self, "distance_place%i" % stage, None)

    def set_distance_total_seconds(self):
        self.distance_total_seconds = sum((time_to_seconds(obj.time) for obj in self.results))

    def get_total_seconds(self):
        return sum((time_to_seconds(obj.time) for obj in self.results)) class HelperResults(TimestampMixin, models.Model):
    """
    This is helper table to calculate number and passage assigning for participants.
    """
    competition = models.ForeignKey('core.Competition')
    participant = models.ForeignKey('registration.Participant')

    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, null=True, blank=True)
    object_id = models.PositiveIntegerField(null=True, blank=True)
    result_used = GenericForeignKey('content_type', 'object_id')

    calculated_total = models.FloatField(blank=True, null=True, db_index=True)

    passage_assigned = models.IntegerField(blank=True, null=True, db_index=True)

    is_manual = models.BooleanField(default=False)  # Manually added records will not be overwritten

    matches_slug = models.SlugField(blank=True) class ChipScan(models.Model):
    competition = models.ForeignKey('core.Competition')
    nr_text = models.CharField(max_length=20)
    time_text = models.CharField(max_length=20)
    nr = models.ForeignKey('registration.Number', blank=True, null=True)
    time = models.TimeField(blank=True, null=True)

    is_processed = models.BooleanField(default=False)
    is_blocked = models.BooleanField(default=False)

    url_sync = models.ForeignKey(UrlSync, blank=True, null=True) class Result(models.Model):
    STATUSES = (
        ('DSQ', 'DSQ'),
        ('DNS', 'DNS'),
        ('DNF', 'DNF'),
    )
    competition = models.ForeignKey('core.Competition')
    participant = models.ForeignKey('registration.Participant')
    number = models.ForeignKey('registration.Number')

    time = models.TimeField(_('Time'), blank=True, null=True)

    zero_time = models.TimeField(_('Time'), blank=True, null=True)  # not used for SEB
    chip_time = models.TimeField(_('Time'), blank=True, null=True)  # not used for SEB

    # lap_count = models.IntegerField(default=0)  # not used for SEB
    # loses_group = models.TimeField(_('Loses Group'), blank=True, null=True)
    # loses_distance = models.TimeField(_('Loses Distance'), blank=True, null=True)

    avg_speed = models.FloatField(_('Average Speed'), blank=True, null=True)

    result_group = models.IntegerField(_('Result Group'), blank=True, null=True)
    result_distance = models.IntegerField(_('Result Distance'), blank=True, null=True)

    points_group = models.IntegerField(_('Points Group'), default=0)
    points_distance = models.IntegerField(_('Points Distance'), default=0)

    status = models.CharField(_('Status'), max_length=20, choices=STATUSES, blank=True)

    standings_content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, null=True, blank=True)
    standings_object_id = models.PositiveIntegerField(null=True, blank=True)
    standings_object = GenericForeignKey('standings_content_type', 'standings_object_id')

    leader = models.ForeignKey('results.Leader', blank=True, null=True)

    _competition_class = None

    class Meta:
        unique_together = (('competition', 'participant', 'number', ), )

    # def set_loses_distance(self):
    #     r = Result.objects.filter(competition=self.competition, number__distance=self.number.distance).order_by('time')
    #     if r:
    #         zero_time = datetime.datetime.combine(datetime.date.today(), datetime.time(0, 0, 0, 0))
    #         delta = datetime.datetime.combine(datetime.date.today(), r[0].time) - zero_time
    #         self.loses_distance = datetime.datetime.combine(datetime.date.today(), self.time) - delta
    #     else:
    #         self.loses_distance = '00:00:00'
    #
    # def set_loses_group(self):
    #     r = Result.objects.filter(competition=self.competition, number__distance=self.number.distance, participant__group=self.participant.group).order_by('time')
    #     if r:
    #         zero_time = datetime.datetime.combine(datetime.date.today(), datetime.time(0, 0, 0, 0))
    #         delta = datetime.datetime.combine(datetime.date.today(), r[0].time) - zero_time
    #         self.loses_group = datetime.datetime.combine(datetime.date.today(), self.time) - delta
    #     else:
    #         self.loses_group = '00:00:00'

    def set_avg_speed(self):
        avg_speed = self.avg_speed
        if self.time:
            admin = DistanceAdmin.objects.get(competition=self.competition, distance=self.participant.distance)
            seconds = datetime.timedelta(hours=self.time.hour, minutes=self.time.minute, seconds=self.time.second).seconds
            if admin.distance_actual:
                self.avg_speed = round((float(admin.distance_actual) / float(seconds))*3.6, 1)
            if avg_speed != self.avg_speed:
                return True
        return False

    def get_competition_class(self):
        if not self._competition_class:
            class_ = load_class(self.competition.processing_class)
            self._competition_class = class_(self.competition.id)
        return self._competition_class

    def set_points_distance(self):
        points_distance = self.points_distance
        self.points_distance = self.get_competition_class().calculate_points_distance(self)
        if points_distance != self.points_distance:
            return True
        return False

    def set_points_group(self):
        points_group = self.points_group
        self.points_group = self.get_competition_class().calculate_points_group(self)
        if points_group != self.points_group:
            return True
        return False

    def set_all(self):
        # self.set_loses_distance()
        # self.set_loses_group()
        avg = self.set_avg_speed()
        pd = self.set_points_distance()
        pg = self.set_points_group()
        if avg or pd or pg:
            return True  # if any of variables is updated, then return true
        return False @task(base=LogErrorsTask)
def update_helper_result_table(competition_id, update=False, participant_id=None):

    # Delete duplicate HelperResults if any.
    cursor = connection.cursor()
    cursor.execute("Select participant_id, count(*) from results_helperresults where competition_id=%s group by participant_id having count(*)>1", [competition_id])
    for participant_id, _ in cursor.fetchall():
        HelperResults.objects.filter(competition_id=competition_id, participant_id=participant_id)[0].delete()
    # END Delete duplicate

    competition = Competition.objects.get(id=competition_id)

    participants = Participant.objects.filter(is_participating=True).order_by('distance', 'registration_dt')

    if competition.is_individual:
        participants = participants.filter(competition=competition)
    else:
        participants = participants.filter(competition_id__in=competition.get_ids())

    if not update:
        participants = participants.extra(
            where=[
                "(Select hr1.calculated_total from results_helperresults hr1 where hr1.participant_id=registration_participant.id and hr1.competition_id = %s) is null"
            ], params=[competition_id, ],)

    if participant_id:
        participants = participants.filter(id=participant_id)

    if participants:
        class_ = load_class(competition.processing_class)
        competition_class = class_(competition=competition)

        competition_class.create_helper_results(participants)
import csv
import os
import datetime
import pytz
from difflib import get_close_matches
from io import BytesIO
from django.db import transaction
from django.db.models import Sum
from django.utils import timezone
from reportlab.lib.colors import HexColor
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import cm
from reportlab.pdfgen import canvas
from slugify import slugify
from velo.core.models import Log, Distance
from velo.core.pdf import fill_page_with_image, _baseFontNameB, _baseFontName
from velo.registration.competition_classes.base_seb import SEBCompetitionBase
from velo.registration.models import Application, ChangedName, Number, Participant, UCICategory, PreNumberAssign
from django import forms
from django.utils.translation import ugettext_lazy as _
from velo.registration.tables import ParticipantTableWithPoints, ParticipantTableWithPassage, ParticipantTable
from velo.results.helper import time_to_seconds, seconds_to_time
from velo.results.models import SebStandings, HelperResults, ChipScan, Result
from velo.results.tables import ResultDistanceCheckpointTable, ResultXCODistanceCheckpointSEBTable
from velo.results.tasks import update_helper_result_table
            helper.calculated_total = round(helper.calculated_total, 2)

            helper.save()

    def process_chip_result(self, chip_id, sendsms=True, recalc=False):
        """
        Function processes chip result and recalculates all standings
        """

        chip = ChipScan.objects.get(id=chip_id)

        if chip.is_processed:
            Log.objects.create(content_object=chip, action="Chip process", message="Chip already processed")
            return None

        if chip.url_sync.kind == 'FINISH':
            return super().process_chip_result(chip_id, sendsms, recalc)
        else:
            result_time, seconds = self.calculate_time(chip)

            # Do not process if finished in 10 minutes.
            if seconds < 10 * 60: # 10 minutes
                Log.objects.create(content_object=chip, action="Chip process", message="Chip result less than 10 minutes. Ignoring.")
                return None

            participant = self.process_chip_create_participant(chip)

            if not participant:
                Log.objects.create(content_object=chip, action="Chip error", message="Participant not found")
                return False

            result, created = Result.objects.get_or_create(competition=chip.competition, participant=participant[0], number=chip.nr, )
            lap, created = result.lapresult_set.get_or_create(index=chip.url_sync.index)
            if lap.time and not recalc:
                Log.objects.create(content_object=chip, action="Chip process", message="Lap time already set.")
                return None

            lap.time = result_time
            lap.save()

        print(chip)

    def get_result_table_class(self, distance, group=None):
        if distance.id != self.BERNU_DISTANCE_ID and not group:
            return ResultDistanceCheckpointTable

        return super().get_result_table_class(distance, group)

    def setup(self):
        uci = UCICategory.objects.filter(category="CYCLING FOR ALL", birthday__gte="1982-01-01", birthday__lt="1998-01-01")
        for u in uci:
            Participant.objects.filter(competition=self.competition, distance_id=49, is_participating=True, slug=u.slug, gender="M").update(group='M 19-34 CFA')

    def import_children_csv(self, filename):
        with transaction.atomic():
            with open(filename, 'r') as csvfile:
                results = csv.reader(csvfile)
                next(results)  # header line
                for row in results:

                    nr = int(row[0])
                    first_name = row[1]
                    last_name = row[2]
                    birthyear = int(row[3])
                    team_name = row[4]
                    index = int(row[5])
                    status = row[6]
                    time = row[7]
                    place = row[8]
                    points = row[9]
                    group = row[12]

                    if not place:
                        place = None
                    if not points:
                        points = None

                    print(row)

                    if index != self.competition_index:
                        print("Not processing.")
                        continue

                    if not first_name or not last_name or not birthyear:
                        print("Don't have all required data. Skipping.")
                        continue

                    if not time:
                        print("Don't have time data. Skipping.")
                        continue

                    number = Number.objects.get(distance_id=self.BERNU_DISTANCE_ID, number=nr)
                    if not number.participant_slug:
                        number.participant_slug = slugify("%s-%s-%s" % (row[1], row[2], row[3]), only_ascii=True)
                        number.save()

                    chip = ChipScan(competition=self.competition, nr=number, time=datetime.time(*map(int, time.split(':'))))

                    results = Result.objects.filter(competition=chip.competition, number=chip.nr).exclude(time=None)
                    # if results:
                    #     Log.objects.create(content_object=chip, action="Chip process",
                    #                        message="Chip ignored. Already have result")
                    #     continue
                    if True:
                        participant = self.process_chip_create_participant(chip)

                        # If participant is not previously created in system, we create it using data provided in CSV
                        if not participant:
                            data = {
                                'competition_id': self.competition_id,
                                'distance_id': self.BERNU_DISTANCE_ID,
                                'team_name': team_name,
                                'is_participating': True,
                                'first_name': first_name,
                                'last_name': last_name,
                                'birthday': datetime.date(birthyear, 1, 1),
                                'is_only_year': True,
                                'gender': '',
                                'primary_number': number
                            }

                            participant = [Participant.objects.create(**data), ]