sql >> Base de Datos >  >> RDS >> PostgreSQL

¿Cómo registro clasificaciones diarias para un modelo en Django?

Sugeriría algo similar a lo que e4c5 sugirió , pero también:

  • Genere un índice sobre la fecha de los rangos para que se pueda optimizar la obtención de todos los rangos en un solo día.

  • Marque la fecha y el estudiante como unique_together . Esto evita la posibilidad de registrar dos rangos para el mismo estudiante en la misma fecha.

Los modelos se verían así:

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()

En una aplicación completa, también esperaría tener algunas restricciones de unicidad en Grade y Student pero el problema presentado en la pregunta no proporciona suficientes detalles sobre estos modelos.

Luego podría ejecutar una tarea todos los días con cron o cualquier administrador de tareas que desee usar (Apio también es una opción), para ejecutar un comando como el siguiente que actualizaría los rangos de acuerdo con algún cálculo y purgaría los registros antiguos. El siguiente código es una ilustración de como se puede hacer. El código real debe diseñarse para ser generalmente idempotente (el siguiente código no lo es porque el cálculo de rango es aleatorio) de modo que si el servidor se reinicia en medio de una actualización, el comando simplemente se puede volver a ejecutar. Aquí está el 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 qué no encurtidos?

Varias razones:

  1. Es una optimización prematura y, en general, probablemente no sea una optimización en absoluto. Algunos las operaciones pueden ser más rápidas, pero otras operaciones será más lento. Si los rangos se conservan en un campo en Student entonces, cargar un estudiante específico en la memoria significa cargar toda la información de rango en la memoria junto con ese estudiante. Esto se puede mitigar usando .values() o .values_list() pero entonces ya no obtienes Student instancias de la base de datos. ¿Por qué tener Student instancias en primer lugar y no solo acceder a la base de datos sin procesar?

  2. Si cambio los campos en Rank , las funciones de migración de Django permiten realizar fácilmente los cambios necesarios cuando implemento la nueva versión de mi aplicación. Si la información de clasificación se almacena en un campo, tengo que administrar cualquier cambio de estructura escribiendo un código personalizado.

  3. El software de la base de datos no puede acceder a los valores en un pickle, por lo que debe escribir un código personalizado para acceder a ellos. Con el modelo anterior, si desea enumerar a los estudiantes por rango hoy (y los rangos para hoy ya se calcularon), puede hacer lo siguiente:

    for r in Rank.objects.filter(date=utcnow()).order_by("value")\
        .prefetch_related():
        print r.student.name
    

    Si usa pickles, debe escanear a todos los Students y deseleccione los rangos para extraer el del día que desee, y luego use una estructura de datos de Python para ordenar a los estudiantes por rango. Una vez hecho esto, debe iterar sobre esta estructura para obtener los nombres en orden.