Django F() Object Kullanımı

Kader Miyanyedi
3 min readMay 9, 2022

--

Herkese selamlar! Bugünkü yazımızda django F() objelerini inceleyeceğiz. Öncelikle product isimli bir model oluşturalım:

class Product(models.Model):
name = models.CharField(_("Name"), max_length=32)
count_stock = models.PositiveIntegerField(_("Count stock"), default=0)
price = models.DecimalField(_("Price"))
total_comment = models.PositiveIntegerField(_("Total comment"), default=0)
total_like = models.PositiveIntegerField(_("Total like"), default=0)
is_active = models.BooleanField(_("Is active"), default=True)

Satılan bir ürünün toplam adedini güncellediğimiz bir fonksiyon hazırlayalım.

product = Product.objects.get(pk=1)
product.count_stock -= 1
product.save(update_fields=["count_stock"])

Django’da queryset işlemleri yapılırken model alan bilgileri python belleğine çekilir ve gerekli işlemlerin yapılmasının ardından veri tabanına tekrar yazılır.

Mantık açısından yazılan bu fonksiyonun çalışması gerekmektedir. Ancak burada race condition problemi ile karşı karşıya kalma ihtimalimiz oldukça yüksektir.

Örneğin, toplamda 10 adet ürünümüz olsun ve 2 farklı müşterinin aynı ürünü aldığını varsayalım. 1. müşteri işlemi başladıktan sonra ürün sayısı 1 azalır ve 9 olur. Ancak henüz save() metodu çalışmadığı için bu işlem veri tabanına yansımamıştır. Bu arada 2. müşteri işlemi de başlar ve ürün sayısı bir azalır ve yine 9 olur. Her iki işlemin kayıtları tamamlandıktan sonra veri tabanında toplam ürün sayımız 9 olacaktır.

F() object dökümanda da belirtildiği gibi model alan bilgilerine doğrudan veri tabanı üzerinden ulaşmamızı ve işlemler yapmamızı sağlar. Bu sayede oluşabilecek race condition probleminden kaçınmış oluruz. Ayrıca bilgiler python belleğine çekilmediği için daha hızlıdır. Yukarıdaki örneği F() object kullanarak tekrar yazalım:

from django.db.models import Fproduct = Product.objects.get(pk=1)
product.count_stock = F("count_stock") - 1
product.save(update_fields=["count_stock"])

Not: F() object kullanarak race condition problemini tamamen çözmüş sayılmayız. İşlemlerin transaction içerisinde yapıldığı bir senaryoda F() object kullanılsa bile race condition ile karşılaşabiliriz. Race condition problemini çözmenin bir diğer yolu ise satırları işlem sonuna kadar kitlemektir. Bunun için select_for_update methodu kullanabilirsiniz.

F() object kullanılarak count_stock değerini güncelledikten sonra bu alan gerçek değeri yerine django.db.models.expressions.CombinedExpression örneği tutacaktır. Alanın gerçek değerine ulaşmak için nesnenin refresh_from_db() metodu ile yeniden yüklenmesi gerekmektedir.

F() object model save() işleminden sonra da devam eder. Bunun önüne geçmek içinde save() işlemi sonrasında nesnenin tekrar yüklenmesi gerekir. save() metodu içerisinde güncellenen alanları belirtmek alanın tekrar güncellenmesinin önüne geçer.

from django.db.models import Fproduct = Product.objects.get(pk=1) # count stock 96
product.count_stock = F("count_stock") - 1
product.save() # count stock 95
product.is_active = True
product.save() # count stock 94

F() object farklı queryset fonksiyonları ile birlikte kullanılabilir.

1- F() ve update methodu

Product.objects.update(count_stock = F("count_stock") * 2)

Update metodu ile birden çok nesne güncelleyebiliriz. Bu sayede nesnelerin python belleğine alınmasına ve for döngüsü kurulmasına gerek kalmaz. Bu daha hızlı bir sorgudur.

2- F() ve filter metodu

Bir model alanının değerini aynı modeldeki farklı bir alanla karşılaştırmak istediğimizde F() object kullanabiliriz. F() object model alanı için referans görevi görür ve aynı modeldeki iki alanı karşılaştırmak için filter metodu içerisinde kullanılabilir.

Toplam yorum sayısı, toplam beğeni sayısının 2 katından daha fazla olan ürünleri getirmek için aşağıdaki gibi bir sorgu yazabiliriz:

Product.objects.filter(total_comment__gte=F("total_like") * 2)

3- F() ve annotate metodu

Birden çok model alanı ile annotate işlemi uygulamak istersek F() object kullanabiliriz.

Product.objects.annotate(
total_reaction=(
F("total_comment") + F("total_like")
)
)

Farklı türdeki alanlar üzerinde işlemler yapılacağı zaman Django’ya sonucun hangi türde bir alan olacağını belirtmemiz gerekir. Fakat F() object doğrudan output_field özelliği sağlamaz. Bu sebeple ifadeyi ExpressionWrapper içerisine sarmamız gerekmektedir.

Product.objects.annotate(
value_in_stock=ExpressionWrapper(
F("count_stock") + F("price"), output_field=DecimalField()
)
)

Sonuç olarak F() object kullanımı ile birlikte;

  • save(), update() gibi metodlar çalıştırıldığında veri tabanındaki değer ele alınır. Potansiyel bir race condition problemini önleyebiliriz.
  • Bilgileri python belleğine çekmek yerine veri tabanında seviyesinde işlemleri yapabiliriz. Bu sayede gerekli sorgu sayısını azaltabilir veya daha hızlı sorgular yazabiliriz.

Bir yazının daha sonuna geldik. Keyifli ve faydalı bir yazı olmuştur umarım. Bir sonraki yazıda görüşmek üzere ^^

Kaynaklar

[1] Django F() Expressions
[2] Filter can reference fields on the model
[3] Using F() Expressions

--

--