Atomické get_or_create v djangu

16.11.2013 | 14:06 | Mirecove dristy | Miroslav Bendík

Pozor na funkciu get_or_create v djangu. Navonok sa síce tvári atomicky, ale v skutočnosti môže nastať situácia keď sa vytvoria 2 objekty s rovnakými atribútmi.

Predstavme si, že chceme zaznamenávať štatistiky pre každú URL adresu webu. Štatistiky budú ukladané v ORM modeli PageStatistics. Model bude definovaný nasledovne:

from django.db import models

class PageStatistics(models.Model):
    path = model.CharField(max_length=255)
    access_count = models.IntegerField(default=0)

Pri každom zobrazení stránky sa spustí nasledujúci kód:

stat = PageStatistics.objects.get_or_create(path=request.META['path'])
stat.access_count += 1
stat.save()

Nový riadok v tabuľke sa vytvára až pri volaní metódy save. Ak sa ešte pred zavolaním save v inom vlákne spustí get_or_create vytvoria sa 2 samostatné objekty štatistík s rovnakou URL adresou.

Atomickosť sa dá od verzie django 1.6 dosiahnuť pomerne elegantným kódom:

from django.db import transaction
from django.db.models import F

...
path = request.META['path']
try:
    stat = PageStatistics.objects.get(path=path)
except PageStatistics.DoesNotExist:
    with transaction.atomic():
        stat = PageStatistics(path=path)
        stat.save()
PageStatistics.objects.filter(pk=stat.pk).update(access_count=F('access_count') + 1)