Navrhoval bych něco podobného tomu, co e4c5 navrhl , ale také bych:
-
Vygenerujte index k datu hodnocení, aby bylo možné optimalizovat získání všech hodnocení v kterýkoli den.
-
Označte datum a studenta jako
unique_together
. To zabraňuje možnosti zaznamenat dvě hodnosti pro stejného studenta ve stejné datum.
Modely by vypadaly takto:
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()
V plnohodnotné aplikaci bych také očekával určitá omezení jedinečnosti na Grade
a Student
ale problém uvedený v otázce neposkytuje dostatek podrobností o těchto modelech.
Pomocí cron
pak můžete každý den spouštět úlohu nebo jakéhokoli správce úloh, který chcete použít (Celery je také možnost), ke spuštění příkazu jako je následující, který by aktualizoval pořadí podle nějakého výpočtu a vyčistil staré záznamy. Následující kód je ilustrační jak se to dá udělat. Skutečný kód by měl být navržen tak, aby byl obecně idempotentní (následující kód není proto, že výpočet pořadí je náhodný), takže pokud je server restartován uprostřed aktualizace, lze příkaz pouze znovu spustit. Zde je kód:
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()
Proč ne okurky?
Více důvodů:
-
Je to předčasná optimalizace a celkově pravděpodobně vůbec žádná optimalizace. Některé operace mohou být rychlejší, ale jiné operace bude pomalejší. Pokud jsou hodnosti vybrány do pole na
Student
pak načtení konkrétního studenta do paměti znamená načtení všech informací o hodnosti do paměti společně s tímto studentem. To lze zmírnit pomocí.values()
nebo.values_list()
ale pak už nedostaneteStudent
instance z databáze. Proč mítStudent
instance na prvním místě a ne pouze přístup k nezpracované databázi? -
Pokud změním pole v
Rank
, migrační prostředky Django snadno umožňují provádění potřebných změn při nasazení nové verze mé aplikace. Pokud jsou informace o hodnosti vybrány do pole, musím řídit jakoukoli změnu struktury napsáním vlastního kódu. -
Databázový software nemůže přistupovat k hodnotám v nálevu, takže pro přístup k nim musíte napsat vlastní kód. S výše uvedeným modelem, pokud chcete seznam studentů podle hodnosti dnes (a hodnosti pro dnešek již byly spočítány), můžete:
for r in Rank.objects.filter(date=utcnow()).order_by("value")\ .prefetch_related(): print r.student.name
Pokud používáte okurky, musíte naskenovat všechny
Students
a rozbalte hodnosti, abyste extrahovali hodnosti pro požadovaný den, a pak použijte datovou strukturu Pythonu k seřazení studentů podle hodnosti. Jakmile to uděláte, musíte tuto strukturu iterovat, abyste dostali názvy do pořádku.