Skip to content

Django F Objesi Nedir?

Bir F() nesnesi, bir model alanının değerini, bir model alanının dönüştürülmüş değerini veya eklenmiş bir sütunu temsil eder. Bu nesne, model alanı değerlerine atıfta bulunmayı ve onlar üzerinde veritabanı işlemleri gerçekleştirmeyi sağlar, ancak bunları gerçekten veritabanından Python belleğine çekmeden yapar.

Daha iyi anlamak için bir örnek üzerinden gidelim. Diyelim ki "Ürün" adında bir modelimiz var ve bu modelde "fiyat" adında bir alanımız bulunuyor. Tüm ürünlerin fiyatını %10 artırmak istiyoruz. Her bir ürünü veritabanından alıp Python belleğinde fiyatı güncellemek ve tekrar veritabanına kaydetmek yerine, bir F() nesnesi kullanabiliriz. F() nesnesi sayesinde fiyat artışını doğrudan veritabanında gerçekleştirebilir ve Python belleğini dahil etmeden yapabiliriz.

Django, bu durumda F() nesnesini kullanarak, gerekli işlemi (bu durumda fiyatı %10 artırmayı) veritabanı seviyesinde açıklayan bir SQL ifadesi oluşturur. Bu, veritabanının kendisinin güncelleme işlemini yönettiği anlamına gelir ve aynı alana aynı anda birden fazla işlem müdahale ettiğinde oluşabilecek bir race condition (yarış koşulu) durumunu önler. "Yarış koşulu", birden fazla işlemin aynı anda bir kaynağa erişmeye çalıştığı durumu ifade eder.

Özetlemek gerekirse, F() nesnesi, alanlara atıfta bulunmak, nesneleri belleğe yüklemeden doğrudan veritabanında işlemler gerçekleştirmek ve Django'da SQL ifadeleri oluşturmak için kullanışlıdır. Ayrıca, veritabanında alan değerlerini güncelleme işlemlerinde F() nesnesini kullanmak, yarış koşullarını(race conditions) önlemeye yardımcı olur.

# models.py
class Product(models.Model):
    name = models.CharField(max_length=200)
    stock = models.PositiveIntegerField()
- python manage.py shell diyerek shell ortamında örnekler ile F objesini anlamaya çalışalım

In [1]: from app_f.models import Product
In [2]: from django.db.models import F

# 100 ürün oluştur
In [3]: for i in range(100):
            p = Product.objects.create(name=f"my product {i}", stock=(i+1)*5)

In [4]: Product.objects.count()
Out[4]: 100

# Verileri veritabanından alıp belleğe yüklüyoruz
# Uygulamayı ölçeklendirirken maliyetli olabilir
In [5]: p = Product.objects.get(pk=1) # 1. sorgu
In [6]: p.stock
Out[6]: 5
In [7]: p.stock += 1
In [8]: p.save() # 2. sorgu
In [9]: p.stock
Out[9]: 6

# Son ürünü al
In [34]: p = Product.objects.last()
In [35]: p.stock
Out[35]: 500

# Bu işlem veritabanında gerçekleşir, Python bunun farkında değil !!!
In [36]: p.stock = F('stock') + 333

# Normal bir Python örneğine değer atama gibi görünüyor, aslında bu, veritabanındaki bir
# işlemi tanımlayan bir SQL yapısıdır.
In [37]: p.save()
In [38]: p.stock
Out[38]: <CombinedExpression: F(stock) + Value(333)>
In [39]: p.refresh_from_db() # Yeni değere erişmek için nesne veritabanından tekrar yüklenmelidir
In [40]: p.stock
Out[40]: 833

# Django, F sınıfı aracılığıyla F() örneğiyle karşılaştığında, standart Python operatörlerini
# geçersiz kılarak, kapsüllenmiş bir SQL ifadesi oluşturur; bu durumda, p.stock tarafından
# temsil edilen veritabanı alanını artıran bir ifade.


# p.stock üzerinde ne değer var veya ne olmuş olursa olsun, Python hiçbir zaman ondan haberdar olmaz -
# bununla tamamen veritabanı ilgilenir. Django'nun F sınıfı aracılığıyla Python, yalnızca
# alanı işaretlemek ve işlemi açıklamak için SQL sözdizimini oluşturur.
In [41]: p1 = Product.objects.filter(id=1)
In [42]: p1
Out[42]: <QuerySet [<Product: Product object (1)>]>
In [43]: p1.stock
AttributeError Traceback (most recent call last)
<ipython-input-43-f0ef1b6980ab> in <module>
----> 1 p1.stock
AttributeError: 'QuerySet' object has no attribute 'stock'

In [44]: p1[0].stock
Out[44]: 6
In [45]: p1.update(stock=F('stock')+1000)
Out[45]: 1
In [46]: p1[0].stock
Out[46]: 1006

# İlk 5 ürün adı ve stok değeri
# id name stock
# 1 my product 0 1006
# 2 my product 1 10
# 3 my product 2 15
# 4 my product 3 20
# 5 my product 4 25

# Tüm stok değerlerini 2 ile çarparak güncelle
In [47]: Product.objects.all().update(stock=F('stock')*2)
Out[47]: 100

# İlk 5 ürün adı ve stok değeri
# id name stock
# 1 my product 0 2012
# 2 my product 1 20
# 3 my product 2 30
# 4 my product 3 40
# 5 my product 4 50
- Model alanlarına atanan F() nesneleri, model örneğini kaydettikten sonra devam eder ve her save()'de uygulanır. Dikkat !!!

In [57]: p1 = Product.objects.get(id=1) # Ürün nesnesi "id=1" olan ürünü alır.
In [58]: p1.stock # Stok miktarını getirir.
Out[58]: 2014
In [59]: p1.name # Ürün adını getirir.
Out[59]: 'my first product 0'

In [60]: p1.stock = F("stock")+1 # İlk artış işlemi için stok miktarını bir artırır.
In [61]: p1.save() # Değişiklikleri kaydeder.

In [62]: p1.stock # Stok miktarını getirir.
Out[62]: <CombinedExpression: F(stock) + Value(1)>

In [63]: p1.name = "my first product 0 updated"
In [64]: p1.save() # F nesnesiyle ilişkili olduğu için ikinci artış işlemi gerçekleşir!
In [65]: p1.stock # Stok miktarını getirir.
Out[65]: <CombinedExpression: F(stock) + Value(1)>
In [66]: p1.refresh_from_db()
In [67]: p1.stock # Stok miktarını getirir.
Out[67]: 2016 # 2 kez artırıldı
- Yeni Modeller ve alanlar tanımlayalım

class Store(models.Model):
    name = models.CharField(max_length=40)

class Material(models.Model):
    name = models.CharField(max_length=100)

class Product(models.Model):
    name = models.CharField(max_length=200)
    stock = models.PositiveIntegerField()
    materials = models.ManyToManyField(Material)
    stores = models.ManyToManyField(Store)
- python manage.py shell

In [1]: from app_f.models import *

In [2]: from django.db.models import *

In [3]: for i in range(100):
   ...:     m = Material.objects.create(name=f"material{i}")
   ...: 

In [4]: for i in range(100):
   ...:     s = Store.objects.create(name=f"store{i}")

In [5]: p1 = Product.objects.get(pk=1)
In [10]: p1.materials.set([i for i in Material.objects.filter(id__lt=5)])
In [13]: p1.stores.set([i for i in Store.objects.filter(id__lt=3)])
In [15]: p2 = Product.objects.get(pk=2)
In [16]: p2.materials.set([i for i in Material.objects.filter(id__in=[6,7,8,9])])
In [17]: p2.stores.set([i for i in Store.objects.filter(id__in=[4,5,6,7])])

In [43]: qpm = Product.objects.annotate(num_materials=Count('materials'))
In [44]: qps = Product.objects.annotate(num_stores=Count('stores'))
In [45]: qps[0].num_stores
Out[45]: 2
In [46]: qpm[0].num_materials
Out[46]: 4

- Edit Models

class Material(models.Model):
    name = models.CharField(max_length=100)
    price = models.PositiveIntegerField(default=10) # yeni eklendi
- python manage.py shell

In [3]: m1 = Material.objects.first()
In [4]: m1.price
Out[4]: 10
In [5]: m1.name
Out[5]: 'material0'
In [6]: p1 = Product.objects.first()
In [7]: p1.name
Out[7]: 'my first product 0 updated'
In [8]: p1.stock
Out[8]: 2016
In [10]: qs = Product.objects.annotate(stock_price=F('stock')*F('materials__price'))
In [12]: p1.materials.count()
Out[12]: 4
In [13]: qs[0].stock_price
Out[13]: 20160

# Eğer birleştirdiğiniz alanlar farklı tiplerde ise Django'ya hangi türde bir alanın döneceğini belirtmeniz gerekecektir.
# Çünkü F() doğrudan output_field'u desteklemediğinden, ifadeyi ExpressionWrapper ile sarmalamanız gerekecektir:
In [16]: qs = Product.objects.annotate(
...: stock_price_decimal = ExpressionWrapper(
...: F('stock')*F('materials__price'), output_field=DecimalField()
...: )
...: )

In [17]: qs[0].stock_price_decimal
Out[17]: Decimal('20160')

In [18]: qs[1].stock_price_decimal
Out[18]: Decimal('20160')

In [19]: qs[2].stock_price_decimal
Out[19]: Decimal('20160')

In [20]: qs[3].stock_price_decimal
Out[20]: Decimal('20160')

In [21]: qs[4].stock_price_decimal
Out[21]: Decimal('200')

# F ifadesiyle kullanılan Func
In [22]: qs = Product.objects.annotate(
...: uppername=Func(F('name'), function="Upper")
...: )

In [23]: qs[0].name, qs[0].uppername
Out[23]: ('ilk ürünüm 0 güncellendi', 'İLK ÜRÜNÜM 0 GÜNCELLENDİ')

In [24]: qs[1].name, qs[1].uppername
Out[24]: ('ürünüm 1', 'ÜRÜNÜM 1')


# F ile toplama
In [34]: qs = Product.objects.annotate(total=Sum(F('materials__price'))) # Malzeme fiyatları için toplama

In [35]: qs[0].total
Out[35]: 40

In [36]: qs = Product.objects.annotate(total=Sum(F('materials'))) # ID'ler için toplama

In [37]: qs[0].total
Out[37]: 10

In [38]: qs[0].materials.all()
Out[38]: <QuerySet [<Material: Material object (1)>, <Material: Material object (2)>, <Material: Material object (3)>, <Material: Material object (4)>]>
- ForeignKey gibi ilişkisel alanlara başvururken, F() bir model örneği yerine birincil anahtar değerini döndürür.