Django F Expression Practices
- An
F()object represents the value of a model field, transformed value of a model field, or annotated column. - It makes it possible to refer to model field values and perform database operations using them without actually having to pull them out of the database into Python memory.
- Instead, Django uses the
F()object to generate an SQL expression that describes the required operation at the database level. - F objects or expression used to reference fields
- Helps to avoid loading objects into memory, operations are performed in Database
- Django use F expression to generate an SQL expression
- Another useful benefit of
F()is that having the database - rather than Python - update a field’s value avoids a race condition.
models.py
x
class Product(models.Model): name = models.CharField(max_length=200) stock = models.PositiveIntegerField()python manage.py shell
x
In [1]: from app_f.models import ProductIn [2]: from django.db.models import F# create 100 productIn [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# we are getting data from DB and putting into the memory# when you scale the app this can be costlyIn [5]: p = Product.objects.get(pk=1) # 1. queryIn [6]: p.stockOut[6]: 5In [7]: p.stock += 1In [8]: p.save() # 2. queryIn [9]: p.stockOut[9]: 6# get last productIn [34]: p = Product.objects.last()In [35]: p.stockOut[35]: 500# this happens in DB side, python doesn't know about this !!!In [36]: p.stock = F('stock') + 333 #looks like a normal Python assignment of value to an instance attribute, in fact it’s an # SQL construct describing an operation on the database.In [37]: p.save()In [38]: p.stockOut[38]: <CombinedExpression: F(stock) + Value(333)>In [39]: p.refresh_from_db() #access the new value, the object must be reloaded from dbIn [40]: p.stockOut[40]: 833# When Django encounters an instance of F(), it overrides the standard Python operators to # create an encapsulated SQL expression; in this case, one which instructs the database to # increment the database field represented by p.stock# Whatever value is or was on p.stock, Python never gets to know about it - # it is dealt with entirely by the database. All Python does, through Django’s F() class, # is create the SQL syntax to refer to the field and describe the operation.#In [41]: p1 = Product.objects.filter(id=1)In [42]: p1Out[42]: <QuerySet [<Product: Product object (1)>]>In [43]: p1.stock---------------------------------------------------------------------------AttributeError Traceback (most recent call last)<ipython-input-43-f0ef1b6980ab> in <module>----> 1 p1.stockAttributeError: 'QuerySet' object has no attribute 'stock'In [44]: p1[0].stockOut[44]: 6In [45]: p1.update(stock=F('stock')+1000)Out[45]: 1In [46]: p1[0].stockOut[46]: 1006# first 5 product names and stocksid name stock---------------------------1 my product 0 10062 my product 1 103 my product 2 154 my product 3 205 my product 4 25# update all stock values, by multiplying 2In [47]: Product.objects.all().update(stock=F('stock')*2)Out[47]: 100# first 5 product names and stocksid name stock---------------------------1 my product 0 20122 my product 1 203 my product 2 304 my product 3 405 my product 4 50F()objects assigned to model fields persist after saving the model instance and will be applied on eachsave(). So be careful !!!
x
In [57]: p1 = Product.objects.get(id=1)In [58]: p1.stockOut[58]: 2014In [59]: p1.nameOut[59]: 'my first product 0'In [60]: p1.stock = F("stock")+1 # First incrementationIn [61]: p1.save()In [62]: p1.stockOut[62]: <CombinedExpression: F(stock) + Value(1)>In [63]: p1.name = "my first product 0 updated" In [64]: p1.save() # Second incerementation due to F object persisted model instance!!!In [65]: p1.stockOut[65]: <CombinedExpression: F(stock) + Value(1)>In [66]: p1.refresh_from_db()In [67]: p1.stockOut[67]: 2016 # incremented 2 times- new models and fields
xxxxxxxxxxclass 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)shell
xxxxxxxxxxIn [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_storesOut[45]: 2In [46]: qpm[0].num_materialsOut[46]: 4edit models.py
xxxxxxxxxxclass Material(models.Model): name = models.CharField(max_length=100) price = models.PositiveIntegerField(default=10) # addedshell
x
In [3]: m1 = Material.objects.first()In [4]: m1.priceOut[4]: 10In [5]: m1.nameOut[5]: 'material0'In [6]: p1 = Product.objects.first()In [7]: p1.nameOut[7]: 'my first product 0 updated'In [8]: p1.stockOut[8]: 2016In [10]: qs = Product.objects.annotate(stock_price=F('stock')*F('materials__price'))In [12]: p1.materials.count()Out[12]: 4In [13]: qs[0].stock_priceOut[13]: 20160# If the fields that you’re combining are of different types you’ll need to tell Django# what kind of field will be returned. Since F() does not directly support output_field # you will need to wrap the expression with ExpressionWrapper:In [16]: qs = Product.objects.annotate( ...: stock_price_decimal = ExpressionWrapper( ...: F('stock')*F('materials__price'), output_field=DecimalField() ...: ) ...: )In [17]: qs[0].stock_price_decimalOut[17]: Decimal('20160')In [18]: qs[1].stock_price_decimalOut[18]: Decimal('20160')In [19]: qs[2].stock_price_decimalOut[19]: Decimal('20160')In [20]: qs[3].stock_price_decimalOut[20]: Decimal('20160')In [21]: qs[4].stock_price_decimalOut[21]: Decimal('200')# Func using with F expressionIn [22]: qs = Product.objects.annotate( ...: uppername=Func(F('name'), function="Upper") ...: )In [23]: qs[0].name, qs[0].uppernameOut[23]: ('my first product 0 updated', 'MY FIRST PRODUCT 0 UPDATED')In [24]: qs[1].name, qs[1].uppernameOut[24]: ('my product 1', 'MY PRODUCT 1')# Sum with FIn [34]: qs = Product.objects.annotate(total=Sum(F('materials__price'))) # Sum for material pricesIn [35]: qs[0].totalOut[35]: 40In [36]: qs = Product.objects.annotate(total=Sum(F('materials'))) # Sum for ID'sIn [37]: qs[0].totalOut[37]: 10In [38]: qs[0].materials.all()Out[38]: <QuerySet [<Material: Material object (1)>, <Material: Material object (2)>, <Material: Material object (3)>, <Material: Material object (4)>]>- When referencing relational fields such as
ForeignKey,F()returns the primary key value rather than a model instance.
Yorumlar