PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Como faço para registrar rankings diários para um modelo no Django?


Eu sugeriria algo semelhante ao que e4c5 sugeriu , mas eu também:

  • Gere um índice na data das classificações para que a obtenção de todas as classificações em um único dia possa ser otimizada.

  • Marque a data e o aluno como unique_together . Isso impede a possibilidade de registrar duas classificações para o mesmo aluno na mesma data.

Os modelos ficariam assim:
from django.db import models

class Grade(models.Model):
    pass  # Whatever you need here...

class Student(models.Model):
    name = models.CharField(max_length=20)
    grade = models.ForeignKey(Grade)

class Rank(models.Model):

    class Meta(object):
        unique_together = (("date", "student"), )

    date = models.DateField(db_index=True)
    student = models.ForeignKey(Student)
    value = models.IntegerField()

Em um aplicativo completo, eu também esperaria ter algumas restrições de exclusividade em Grade e Student mas o problema apresentado na pergunta não fornece detalhes suficientes sobre esses modelos.

Você poderia então executar uma tarefa todos os dias com cron ou qualquer gerenciador de tarefas que você queira usar (Aipo também é uma opção), para executar um comando como o seguinte que atualizaria as classificações de acordo com algum cálculo e limparia os registros antigos. O código a seguir é uma ilustração de como isso pode ser feito. O código real deve ser projetado para ser geralmente idempotente (o código a seguir não é porque o cálculo de classificação é aleatório) para que, se o servidor for reinicializado no meio de uma atualização, o comando possa ser executado novamente. Aqui está o código:
import random
import datetime
from optparse import make_option
from django.utils.timezone import utc

from django.core.management.base import BaseCommand
from school.models import Rank, Student

def utcnow():
    return datetime.datetime.utcnow().replace(tzinfo=utc)

class Command(BaseCommand):
    help = "Compute ranks and cull the old ones"
    option_list = BaseCommand.option_list + (
        make_option('--fake-now',
                    default=None,
                    help='Fake the now value to X days ago.'),
    )

    def handle(self, *args, **options):
        now = utcnow()
        fake_now = options["fake_now"]
        if fake_now is not None:
            now -= datetime.timedelta(days=int(fake_now))
            print "Setting now to: ", now

        for student in Student.objects.all():
            # This simulates a rank computation for the purpose of
            # illustration.
            rank_value = random.randint(1, 1000)
            try:
                rank = Rank.objects.get(student=student, date=now)
            except Rank.DoesNotExist:
                rank = Rank(
                    student=student, date=now)
            rank.value = rank_value
            rank.save()

        # Delete all ranks older than 180 days.
        Rank.objects.filter(
            date__lt=now - datetime.timedelta(days=180)).delete()

Por que não picles?


Vários motivos:

  1. É uma otimização prematura e, em geral, provavelmente não é uma otimização. Alguns as operações podem ser mais rápidas, mas outras operações será mais lento. Se as classificações forem selecionadas em um campo em Student então, carregar um aluno específico na memória significa carregar todas as informações de classificação na memória junto com esse aluno. Isso pode ser mitigado usando .values() ou .values_list() mas você não está mais recebendo Student instâncias do banco de dados. Por que Student instâncias em primeiro lugar e não apenas acessar o banco de dados bruto?

  2. Se eu alterar os campos em Rank , as facilidades de migração do Django permitem facilmente realizar as mudanças necessárias quando eu implemento a nova versão do meu aplicativo. Se as informações de classificação forem armazenadas em um campo, tenho que gerenciar qualquer alteração de estrutura escrevendo código personalizado.

  3. O software de banco de dados não pode acessar valores em um pickle e, portanto, você precisa escrever um código personalizado para acessá-los. Com o modelo acima, se você deseja listar os alunos por classificação hoje (e as classificações de hoje já foram calculadas), você pode fazer:
    for r in Rank.objects.filter(date=utcnow()).order_by("value")\
        .prefetch_related():
        print r.student.name
    

    Se você usa picles, precisa escanear todos os Students e descompacte as classificações para extrair a do dia desejado e, em seguida, use uma estrutura de dados Python para ordenar os alunos por classificação. Feito isso, você precisa iterar sobre essa estrutura para obter os nomes em ordem.