Sugerowałbym coś podobnego do tego, co e4c5 sugerował , ale chciałbym też:
-
Wygeneruj indeks na dzień rang, aby zoptymalizować uzyskanie wszystkich rang każdego dnia.
-
Oznacz datę i ucznia jako
unique_together
. Zapobiega to możliwości zapisania dwóch rang dla tego samego ucznia w tym samym dniu.
Modele wyglądałyby tak:
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()
W pełnoprawnej aplikacji spodziewałbym się również pewnych ograniczeń dotyczących unikalności Grade
i Student
ale problem przedstawiony w pytaniu nie dostarcza wystarczających szczegółów na temat tych modeli.
Możesz wtedy codziennie uruchamiać zadanie za pomocą cron
lub dowolnego menedżera zadań, którego chcesz użyć (celery jest również opcją), aby uruchomić polecenie podobne do poniższego, które zaktualizuje rangi zgodnie z niektórymi obliczeniami i usunie stare rekordy. Poniższy kod jest ilustracją jak można to zrobić. Prawdziwy kod powinien być zaprojektowany tak, aby był ogólnie idempotentny (poniższy kod nie jest, ponieważ obliczanie rangi jest losowe), tak aby w przypadku ponownego uruchomienia serwera w środku aktualizacji polecenie można było po prostu ponownie uruchomić. Oto kod:
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()
Dlaczego nie marynaty?
Wiele powodów:
-
Jest to przedwczesna optymalizacja, która prawdopodobnie wcale nie jest optymalizacją. Niektóre operacje mogą być szybsze, ale inne operacje będzie wolniejszy. Jeśli rangi są zamarynowane w polu na
Student
następnie załadowanie konkretnego ucznia do pamięci oznacza załadowanie wszystkich informacji o randze do pamięci wraz z tym uczniem. Można to złagodzić za pomocą.values()
lub.values_list()
ale wtedy nie otrzymujesz jużStudent
instancje z bazy danych. DlaczegoStudent
instancje w pierwszej kolejności, a nie tylko dostęp do surowej bazy danych? -
Jeśli zmienię pola w
Rank
, narzędzia migracji Django z łatwością pozwalają na wykonanie potrzebnych zmian podczas wdrażania nowej wersji mojej aplikacji. Jeśli informacje o randze są zamarynowane w polu, muszę zarządzać każdą zmianą struktury, pisząc niestandardowy kod. -
Oprogramowanie bazy danych nie może uzyskać dostępu do wartości w marynacie, więc musisz napisać niestandardowy kod, aby uzyskać do nich dostęp. W powyższym modelu, jeśli chcesz wyświetlić listę uczniów według rangi dzisiaj (a rangi na dziś zostały już obliczone), możesz:
for r in Rank.objects.filter(date=utcnow()).order_by("value")\ .prefetch_related(): print r.student.name
Jeśli używasz pikli, musisz zeskanować wszystkich
Students
i rozbierz rangi, aby wyodrębnić ten na dany dzień, a następnie użyj struktury danych Pythona, aby uporządkować uczniów według rang. Gdy to zrobisz, musisz przejść przez tę strukturę, aby uporządkować nazwy.