A feature added in Django 5.0 is GeneratedField
, which allows us to auto-calculate database fields.
Below are 7 short examples. After looking at them, you'll know how to use GeneratedField
to use the database to do calculations for you neatly and extremely quickly.
I've made an optional video guide (featuring me 🏇🏿) here, describing the problem and solution:
In the below examples using GeneratedField
, we will automatically calculate:
For each, we'll:
a) add the model to models.py, and
b) query the model to show how the calculated field is used.
Let's get started 🚀
pip install django
django-admin startproject core .
python manage.py startapp sim
python -m django --version
You should see >= 5.0.
# settings.py
INSTALLED_APPS = [
'sim',
...
]
Next up, here's an example of how to use GeneratedField
to automatically concatenate strings from different fields.
sim/models.py
:from django.db import models
from django.db.models.functions import Substr, Concat
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
full_name = models.GeneratedField(
expression=Concat('first_name', models.Value(' '), 'last_name'),
output_field=models.CharField(max_length=100),
db_persist=True
)
initials = models.GeneratedField(
expression=Concat(
Substr('first_name', 1, 1), Substr('last_name', 1, 1)),
output_field=models.CharField(max_length=2),
db_persist=True
)
python manage.py makemigrations
python manage.py migrate
from sim.models import Person
Person.objects.create(first_name='Robert', last_name='Katz')
Person.objects.create(first_name='Alexander', last_name='Henry')
Person.objects.all().values('full_name', 'initials')
>>> <QuerySet [{'full_name': 'Robert Katz', 'initials': 'RK'}, {'full_name': 'Alexander Henry', 'initials': 'AH'}]>
Here's a simple example, which would have been very useful for when I was building schedules and calendars for one of my previous startups (HR tech). I used a combination of app code and fairly complex SQL to do date calculations. GeneratedField simplifies this a lot.
Let's build it 👷:
sim/models.py
:from django.db import models
from django.db.models.functions import Extract
class Event(models.Model):
start_time = models.DateTimeField()
day_of_week = models.GeneratedField(
expression=Extract('start_time', 'week_day'),
output_field=models.IntegerField(),
db_persist=True
)
is_weekend = models.GeneratedField(
expression=models.Case(
models.When(day_of_week__in=[1, 7], then=True),
default=False,
output_field=models.BooleanField(),
),
db_persist=True,
output_field=models.BooleanField(),
)
python manage.py makemigrations
python manage.py migrate
from sim.models import Event
from datetime import datetime
Event.objects.create(start_time=datetime(2022, 1, 1, 12, 0, 0)) # Saturday.
Event.objects.create(start_time=datetime(2022, 1, 2, 12, 0, 0)) # Sunday.
Event.objects.create(start_time=datetime(2022, 1, 3, 12, 0, 0)) # Monday.
Event.objects.all().values('day_of_week', 'is_weekend')
>>> QuerySet [{'day_of_week': 7, 'is_weekend': True}, {'day_of_week': 1, 'is_weekend': True}, {'day_of_week': 2, 'is_weekend': False}]>
Note that SQL databases use 7 for Saturday, 1 for Sunday. So, 2 is Monday.
Next up, here's an example of how to use GeneratedField
to calculate exchange rates automatically.
This would have been useful for me when I was building a hedge fund's infrastructure.
sim/models.py
with the following model:from django.db import models
class Asset(models.Model):
ticker = models.CharField(max_length=10)
price_eur = models.DecimalField(max_digits=10, decimal_places=2)
eur_to_usd_exchange_rate = models.DecimalField(max_digits=5, decimal_places=2, default=1.1)
price_usd = models.GeneratedField(
expression=models.F("price_eur") * models.F("eur_to_usd_exchange_rate"),
output_field=models.DecimalField(max_digits=12, decimal_places=2),
db_persist=True
)
python manage.py makemigrations
python manage.py migrate
from sim.models import Asset
Asset.objects.create(ticker='MSFT', price_eur=121.32, eur_to_usd_exchange_rate=1.1)
Asset.objects.create(ticker='GOOG', price_eur=2.23, eur_to_usd_exchange_rate=1.1)
Asset.objects.all().values('ticker', 'price_eur', 'price_usd')
>>> <QuerySet [{'ticker': 'MSFT', 'price_eur': Decimal('121.32'), 'price_usd': Decimal('133.45')}, {'ticker': 'GOOG', 'price_eur': Decimal('2.23'), 'price_usd': Decimal('2.45')}]>
This shows how to use GeneratedField
to calculate the duration of service for employees.
Another example that would have been useful for me when building a previous product in HR tech to calculate employee length of service.
sim/models.py
with:from django.db import models
class Employee(models.Model):
hire_date = models.DateField()
most_recent_work_date = models.DateField(null=True)
service_period = models.GeneratedField(
expression=models.F('most_recent_work_date') - models.F('hire_date'),
output_field=models.DurationField(),
db_persist=True
)
python manage.py makemigrations
python manage.py migrate
from sim.models import Employee
from datetime import date
Employee.objects.create(hire_date=date(2010, 1, 1), most_recent_work_date=date(2030, 2, 1))
Employee.objects.create(hire_date=date(2026, 1, 10), most_recent_work_date=date(2026, 3, 2))
Employee.objects.values('hire_date', 'service_period')
>>> <QuerySet [{'hire_date': datetime.date(2010, 1, 1), 'service_period': datetime.timedelta(days=7336)}, {'hire_date': datetime.date(2026, 1, 10), 'service_period': datetime.timedelta(days=51)}]>
sim/models.py
with:from django.db import models
from django.db.models import Case, When, Value, BooleanField
class Order(models.Model):
quantity = models.IntegerField()
unit_price = models.DecimalField(max_digits=10, decimal_places=2)
discount_per_unit = models.GeneratedField(
expression=Case(
When(quantity__gt=100, then=models.F("unit_price") * 0.1),
default=Value(0),
output_field=models.DecimalField(max_digits=12, decimal_places=2)
),
db_persist=True,
output_field=models.DecimalField(max_digits=12, decimal_places=2),
)
total_price = models.GeneratedField(
expression=(models.F("quantity") * models.F("unit_price")) -
(models.F("quantity") * models.F("discount_per_unit")),
output_field=models.DecimalField(max_digits=12, decimal_places=2),
db_persist=True
)
python manage.py makemigrations
python manage.py migrate
from sim.models import Order
Order.objects.create(quantity=150, unit_price=10)
Order.objects.create(quantity=50, unit_price=10)
Order.objects.all().values()
>>> <QuerySet [{'id': 1, 'quantity': 150, 'unit_price': Decimal('10.00'), 'discount_per_unit': Decimal('1.00'), 'total_price': Decimal('1350.00')}, {'id': 2, 'quantity': 50, 'unit_price': Decimal('10.00'), 'discount_per_unit': Decimal('0.00'), 'total_price': Decimal('500.00')}]>
sim/models.py
with:class Patient(models.Model):
weight_kg = models.FloatField()
height_m = models.FloatField()
bmi = models.GeneratedField(
expression=models.F('weight_kg') / (models.F('height_m') * models.F('height_m')),
output_field=models.FloatField(),
db_persist=True
)
python manage.py makemigrations
python manage.py migrate
from sim.models import Patient
Patient.objects.create(weight_kg=70, height_m=1.75)
Patient.objects.all().values('bmi')
>>> <QuerySet [{'bmi': 22.857142857142858}]>
Most people like the thought of their money growing exponentially. Here's an example of how to use GeneratedField
to calculate compound interest.
sim/models.py
with:from django.db import models
from django.db.models.functions import Power
class Investment(models.Model):
principal_amount = models.DecimalField(max_digits=10, decimal_places=2)
annual_interest_rate = models.FloatField()
years = models.IntegerField()
future_value = models.GeneratedField(
expression=models.ExpressionWrapper(
models.F('principal_amount') * Power(models.F('annual_interest_rate') + 1, models.F('years')),
output_field=models.DecimalField(max_digits=15, decimal_places=2)
),
db_persist=True,
output_field=models.DecimalField(max_digits=15, decimal_places=2),
)
python manage.py makemigrations
python manage.py migrate
from sim.models import Investment
Investment.objects.create(principal_amount=1000, annual_interest_rate=0.05, years=10)
Investment.objects.all().values('future_value')
>>> <QuerySet [{'future_value': Decimal('1628.89')}]>
GeneratedField
in Django 🎉This is a powerful tool to simplify your code by moving calculations easily to the database, wrapped in the simple interface of the Django ORM.
I want to release high-quality products as soon as possible. Probably like you, I want to make my Django product ideas become real as soon as possible.
That's why I built Photon Designer - an entirely visual editor for building Django frontend at the speed that light hits your eyes. Photon Designer outputs neat, clean Django templates 💡