2016-12-29 17 views
1

Я пытаюсь создать таблицу, которая будет агрегировать статистику на основе идентификатора пользователя. Это первый раз задавая вопрос, так что несите меня, если я пропущу что-нибудь важное.Django Calculate Aggregated Totals

Вот модель:

class Batting(models.Model): 
    player = models.ForeignKey(
    'Player', 
    on_delete=models.CASCADE, 
    null=True, 
    ) 
    game = models.ForeignKey(
    'Game', 
    on_delete=models.CASCADE, 
    null=True, 
    blank=True, 
    ) 
    season = models.ForeignKey(
    'Season', 
    on_delete=models.CASCADE, 
    null=True, 
    ) 
    games_played = models.IntegerField(
    blank=True, 
    default=1, 
    ) 
    plate_appearances = models.IntegerField(
    blank=True, 
    default=0, 
    ) 
    at_bats = models.DecimalField(
    default=0, 
    max_digits=6, 
    decimal_places=3, 
    ) 
    hits = models.DecimalField(
    blank=True, 
    max_digits=6, 
    decimal_places=3, 
    default=0, 
    ) 
    ... 
    batting_average = models.DecimalField(
    max_digits=6, 
    decimal_places=3, 
    editable=False, 
    null=True 
    ) 
    slugging_percentage = models.DecimalField(
    max_digits=6, 
    decimal_places=3, 
    editable=False, 
    null=True 
    ) 
    on_base_percentage = models.DecimalField(
    max_digits=6, 
    decimal_places=3, 
    editable=False, 
    null=True 
    ) 
    on_base_plus_slugging_percentage = models.DecimalField(
    max_digits=6, 
    decimal_places=3, 
    editable=False, 
    null=True 
    ) 

    def save(self, *args, **kwargs): 
     self.total_bases = self.singles + (self.doubles * 2) + (self.triples * 3) + (self.home_runs * 4) 
     self.extra_base_hits = self.doubles + self.triples + self.home_runs 
     self.batting_average = float(self.hits)/float(self.at_bats) 
     self.slugging_percentage = self.total_bases/float(self.at_bats) 
     self.on_base_percentage = (self.hits + self.base_on_balls + self.hit_by_pitch)/float(self.at_bats + self.base_on_balls + self.sacrifice_flys + self.hit_by_pitch) 
     self.on_base_plus_slugging_percentage = (self.hits + self.base_on_balls + self.hit_by_pitch)/float(self.at_bats + self.base_on_balls + self.sacrifice_flys + self.hit_by_pitch) 
     super(Batting, self).save(*args, **kwargs) 

    class Meta: 
    verbose_name = u'Batting Stat' 
    verbose_name_plural = u'Batting Stats' 

    def __unicode__(self): 
    return self.player.last_name 

Вот вид:

def stats(request): 
    batting_stats = Batting.objects.values('player__id').annotate(
    fn=F('player__first_name'), 
    ln=F('player__last_name'), 
    total_hits=Sum(('hits'),output_field=FloatField()), 
    total_at_bats=Sum(('at_bats'),output_field=FloatField()), 
    avg=Avg('batting_average'), 
    slg=Avg('slugging_percentage'), 
    obp=Avg('on_base_percentage'), 
    ops=Avg('on_base_plus_slugging_percentage'), 
    ) 

    return render(request, 'stats.html', { 
    'batting_stats': batting_stats, 
    }) 

Проблема я столкнулся в том, что в среднем, SLG, ОБП, и OPS все расчеты в модели , а усреднение их в представлении - это усреднение средних значений, а не вычисление суммируемых сумм на основе идентификатора игрока.

Я пробовал использовать функцию агрегации, но не нашел способ заставить ее работать. Есть ли способ объединить использование аннотатов и агрегатов, которые позволят мне консолидировать статистику под одним идентификатором игрока и применять те же вычисления к итоговым?

ответ

0

Как я вижу, на самом деле вам нужно выполнить те же вычисления для одного экземпляра Batting и для вашего представления статистики. Так что я буду делать это в одну функцию и вызывать ее из двух точек:

def count_averages(data, result_recipient=None): 
    result_recipient = data if result_recipient=None else result_recipient 
    result_recipient.total_bases = data.singles + (data.doubles * 2) + (data.triples * 3) + (data.home_runs * 4) 
    result_recipient.extra_base_hits = data.doubles + data.triples + data.home_runs 
    ... 

def save(self, *args, **kwargs): 
    count_averages(self) 
    super(Batting, self).save(*args, **kwargs) 

def stats(request): 
    sum_of_all_batting_stats = Batting.objects.filter('player__id'=player__id).annotate(
     sum('singles'), 
     sum('doubles'), 
     sum('triples'), 
     ... 
    ) 
    batting_stats = {} 
    count_averages(self, batting_stats) 

    return render(request, 'stats.html', { 
     'batting_stats': batting_stats, 
    }) 

Это не 100% правильный код, но вы должны есть идея: вы перемещаете ваши расчеты отделенной функции и вызова в 2 разных данных - 1) один экземпляр Batting и 2) сумма всех экземпляров Batting.

P.S. Если вы каждый раз вычисляете его для каждого вызова вида, это может вызвать проблемы с производительностью. Я бы рекомендовал хранить агрегированные данные в таблице db или в кеше.

+0

Спасибо за ответ - это очень похожий ответ, который я получил вчера вечером с другом, поэтому я думаю, что у меня есть хорошая идея, как подойти к нему (с помощью вашего примера). – whob13