Поле GeneratedField

Поле GeneratedField позволяет создавать в модели Django поля, значения которых вычисляются непосредственно на уровне базы данных. Данное поле принимает два обязательных параметра: выражение (expression), определяющее логику вычисления, и параметр output_field, задающий тип данных результата. Значения такого поля обновляются автоматически при изменении данных, от которых оно зависит, и не могут быть изменены вручную через ORM. Поле поддерживается PostgreSQL (начиная с версии 12), MariaDB (10.2+), MySQL (5.7+) и SQLite (3.31+).

Синтаксис

pythonfrom django.db import models
from django.db.models import F, Value
from django.db.models.functions import Concat

class ModelName(models.Model):
  field_name = models.GeneratedField(
    expression = Expression,
    output_field = models.FieldType(),
    db_persist = True / False
  )

Пример . Создание модели с GeneratedField

Создадим модель Product с вычисляемым полем полной стоимости с учетом налога:

pythonfrom django.db import models
from django.db.models import F
from django.db.models.functions import Round

class Product(models.Model):
  name = models.CharField(max_length=100)
  price = models.DecimalField(max_digits=10, decimal_places=2)
  tax_rate = models.DecimalField(max_digits=5, decimal_places=2, default=0.13)
  price_with_tax = models.GeneratedField(
    expression = Round(F('price') * (1 + F('tax_rate')), 2),
    output_field = models.DecimalField(max_digits=12, decimal_places=2),
    db_persist = True
  )

  def __str__(self):
    return self.name

После создания модели поле price_with_tax будет автоматически вычисляться при добавлении или обновлении записей:

pythonfrom myapp.models import Product

product = Product.objects.create(
  name = 'Laptop',
  price = 1000.00,
  tax_rate = 0.13
)

print(product.price_with_tax)

Результат выполнения кода:

python"1130.00"

Пример . С конкатенацией строк

Используем GeneratedField для создания полного имени пользователя из двух отдельных полей:

pythonfrom django.db import models
from django.db.models.functions import Concat
from django.db.models import Value

class User(models.Model):
  first_name = models.CharField(max_length=50)
  last_name = models.CharField(max_length=50)
  full_name = models.GeneratedField(
    expression = Concat(F('first_name'), Value(' '), F('last_name')),
    output_field = models.CharField(max_length=101),
    db_persist = True
  )

  def __str__(self):
    return self.full_name

Теперь при создании пользователя поле full_name заполняется автоматически:

pythonfrom myapp.models import User

user = User.objects.create(
  first_name = 'John',
  last_name = 'Doe'
)

print(user.full_name)

Результат выполнения кода:

python"John Doe"

Пример . С вычислением возраста

Создадим поле, которое будет вычислять возраст пользователя на основе даты рождения и текущей даты:

pythonfrom django.db import models
from django.db.models import F, Value
from django.db.models.functions import ExtractYear, Cast, Now
from django.db.models import IntegerField

class Person(models.Model):
  name = models.CharField(max_length=100)
  birth_date = models.DateField()
  age = models.GeneratedField(
    expression = ExtractYear(Now()) - ExtractYear(F('birth_date')),
    output_field = models.IntegerField(),
    db_persist = False
  )

  def __str__(self):
    return self.name

Обратите внимание, что параметр db_persist установлен в False, так как возраст зависит от текущей даты и должен вычисляться при каждом запросе, а не сохраняться в базе данных:

pythonfrom myapp.models import Person
from datetime import date

person = Person.objects.create(
  name = 'Alice',
  birth_date = date(1990, 5, 15)
)

print(f"{person.name} is {person.age} years old")

Результат выполнения кода:

python"Alice is 36 years old"

Пример . С условным вычислением

Используем условное выражение для вычисления статуса заказа на основе суммы:

pythonfrom django.db import models
from django.db.models import F, Value, Case, When
from django.db.models.functions import Round

class Order(models.Model):
  customer_name = models.CharField(max_length=100)
  amount = models.DecimalField(max_digits=10, decimal_places=2)
  status = models.GeneratedField(
    expression = Case(
      When(amount__gte=1000.00, then=Value('Premium')),
      When(amount__gte=500.00, then=Value('Standard')),
      default=Value('Basic'),
      output_field=models.CharField(max_length=20)
    ),
    output_field = models.CharField(max_length=20),
    db_persist = True
  )

  def __str__(self):
    return f"{self.customer_name} - {self.status}"

При создании заказов поле status будет автоматически определяться на основе суммы:

pythonfrom myapp.models import Order

Order.objects.create(customer_name = 'Bob', amount = 1200.00)
Order.objects.create(customer_name = 'Charlie', amount = 750.00)
Order.objects.create(customer_name = 'Dave', amount = 300.00)

for order in Order.objects.all():
  print(f"{order.customer_name}: {order.status}")

Результат выполнения кода:

python"Bob: Premium"
"Charlie: Standard"
"Dave: Basic"

Смотрите также

  • поле CharField,
    которое используется для хранения строковых данных
  • поле DecimalField,
    которое используется для хранения чисел с плавающей точкой
  • поле DateField,
    которое используется для хранения дат
  • поле IntegerField,
    которое используется для хранения целых чисел