Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions readthedocs/builds/managers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
"""Build and Version class model Managers."""

import hashlib
import json

import structlog
from django.core.exceptions import ObjectDoesNotExist
from django.db import models
Expand Down Expand Up @@ -141,3 +144,19 @@ def register_match(self, rule, version, max_registers=15):
for match in self.filter(rule__project=rule.project)[max_registers:]:
match.delete()
return created


class BuildConfigManager(models.Manager):
"""Manager for BuildConfig model."""

def get_or_create(self, **kwargs):
data = kwargs.get("data")
if data:
dump = json.dumps(data, sort_keys=False)
data_hash = hashlib.sha256(dump.encode("utf-8")).hexdigest()

return super().get_or_create(
data_hash=data_hash,
defaults={"data": data},
)
return super().get_or_create(**kwargs)
29 changes: 29 additions & 0 deletions readthedocs/builds/migrations/0067_buildconfig_data_hash.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Generated by Django 5.2.9 on 2026-01-21 15:23

from django.db import migrations
from django.db import models
from django_safemigrate import Safe


class Migration(migrations.Migration):
safe = Safe.before_deploy()

dependencies = [
("builds", "0066_add_buildconfig_model"),
]

operations = [
migrations.AddField(
model_name="buildconfig",
name="data_hash",
field=models.CharField(editable=False, max_length=64, null=True, unique=True),
),
Comment on lines +16 to +20
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to create a data migration for all these objects to calculate this hash and save it.

migrations.AlterField(
model_name="buildconfig",
name="data",
field=models.JSONField(
help_text="The rendered YAML configuration used in the build",
verbose_name="Configuration data",
),
),
]
15 changes: 14 additions & 1 deletion readthedocs/builds/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from readthedocs.builds.constants import STABLE
from readthedocs.builds.constants import VERSION_TYPES
from readthedocs.builds.managers import AutomationRuleMatchManager
from readthedocs.builds.managers import BuildConfigManager
from readthedocs.builds.managers import ExternalBuildManager
from readthedocs.builds.managers import ExternalVersionManager
from readthedocs.builds.managers import InternalBuildManager
Expand Down Expand Up @@ -617,10 +618,22 @@ class BuildConfig(TimeStampedModel):

data = models.JSONField(
_("Configuration data"),
unique=True,
help_text=_("The rendered YAML configuration used in the build"),
)

# Used to quickly identify identical configurations.
# Using `unique=True` on a JSONField can be problematic due to limitations on its size,
# so we use a hash instead.
data_hash = models.CharField(
unique=True,
max_length=64,
null=True,
editable=False,
)

# Manager to handle creation based on data hash uniqueness.
objects = BuildConfigManager()

class Meta:
verbose_name = _("Build configuration")
verbose_name_plural = _("Build configurations")
Expand Down