Jag skulle föreslå något liknande det som e4c5 föreslog , men jag skulle också:
-
Generera ett index på datumet för rankningarna så att alla rankningar på en enskild dag kan optimeras.
-
Markera datumet och studenten som
unique_together
. Detta förhindrar möjligheten att spela in två rankningar för samma elev på samma datum.
Modellerna skulle se ut så här:
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()
I en fullfjädrad applikation skulle jag också förvänta mig att ha några unika begränsningar på Grade
och Student
men problemet som presenteras i frågan ger inte tillräckligt med detaljer om dessa modeller.
Du kan sedan köra en uppgift varje dag med cron
eller vilken aktivitetshanterare du än vill använda (Sellery är också ett alternativ), för att köra ett kommando som det följande som skulle uppdatera rangordningen enligt någon beräkning och rensa de gamla posterna. Följande kod är en illustration hur det kan göras. Den riktiga koden bör utformas för att vara allmänt idempotent (följande kod beror inte på att rangberäkningen är slumpmässig) så att om servern startas om mitt i en uppdatering kan kommandot bara köras om. Här är koden:
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()
Varför inte pickles?
Flera skäl:
-
Det är en för tidig optimering, och överlag förmodligen ingen optimering alls. Vissa operationer kan vara snabbare, men andra operationer kommer att gå långsammare. Om rangen är inlagda i ett fält på
Student
sedan, att ladda en specifik elev i minnet innebär att ladda all ranginformation i minnet tillsammans med den eleven. Detta kan mildras genom att använda.values()
eller.values_list()
men då får du inte längreStudent
instanser från databasen. Varför haStudent
instanser i första hand och inte bara få tillgång till rådatabasen? -
Om jag ändrar fälten i
Rank
, Djangos migreringsfaciliteter gör det enkelt att utföra de nödvändiga ändringarna när jag distribuerar den nya versionen av min applikation. Om ranginformationen är inlagd i ett fält måste jag hantera alla strukturändringar genom att skriva anpassad kod. -
Databasprogramvaran kan inte komma åt värden i en pickle och så du måste skriva anpassad kod för att komma åt dem. Med modellen ovan, om du vill lista elever efter rang idag (och rangordningen för idag har redan beräknats), kan du göra:
for r in Rank.objects.filter(date=utcnow()).order_by("value")\ .prefetch_related(): print r.student.name
Om du använder pickles måste du skanna alla
Students
och ta bort leden för att extrahera den för dagen du vill ha, och använd sedan en Python-datastruktur för att sortera eleverna efter rang. När detta är gjort måste du iterera över denna struktur för att få ordning på namnen.