Employ native PostgreSQL functions for updating object JSON data when adding/removing custom fields

This commit is contained in:
Jeremy Stretch 2025-03-21 16:20:59 -04:00
parent 7db0765ed2
commit 48c9cc6182

View File

@ -9,6 +9,8 @@ from django.conf import settings
from django.contrib.postgres.fields import ArrayField from django.contrib.postgres.fields import ArrayField
from django.core.validators import RegexValidator, ValidationError from django.core.validators import RegexValidator, ValidationError
from django.db import models from django.db import models
from django.db.models import F, Func, Value
from django.db.models.expressions import RawSQL
from django.urls import reverse from django.urls import reverse
from django.utils.html import escape from django.utils.html import escape
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
@ -281,12 +283,15 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
Populate initial custom field data upon either a) the creation of a new CustomField, or Populate initial custom field data upon either a) the creation of a new CustomField, or
b) the assignment of an existing CustomField to new object types. b) the assignment of an existing CustomField to new object types.
""" """
if self.default is None:
# We have to convert None to a JSON null for jsonb_set()
value = RawSQL("'null'::jsonb", [])
else:
value = Value(self.default, models.JSONField())
for ct in content_types: for ct in content_types:
model = ct.model_class() ct.model_class().objects.update(
instances = model.objects.exclude(**{'custom_field_data__contains': self.name}) custom_field_data=Func(F('custom_field_data'), Value([self.name]), value, function='jsonb_set')
for instance in instances: )
instance.custom_field_data[self.name] = self.default
model.objects.bulk_update(instances, ['custom_field_data'], batch_size=100)
def remove_stale_data(self, content_types): def remove_stale_data(self, content_types):
""" """
@ -294,11 +299,9 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
no longer assigned to a model, or because it has been deleted). no longer assigned to a model, or because it has been deleted).
""" """
for ct in content_types: for ct in content_types:
if model := ct.model_class(): ct.model_class().objects.update(
instances = model.objects.filter(custom_field_data__has_key=self.name) custom_field_data=F('custom_field_data') - self.name
for instance in instances: )
del instance.custom_field_data[self.name]
model.objects.bulk_update(instances, ['custom_field_data'], batch_size=100)
def rename_object_data(self, old_name, new_name): def rename_object_data(self, old_name, new_name):
""" """