Skip to content
Merged
48 changes: 48 additions & 0 deletions netbox/core/migrations/0019_configrevision_active.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Generated by Django 5.2.5 on 2025-09-09 16:48

from django.db import migrations, models


def get_active(apps, schema_editor):
from django.core.cache import cache
ConfigRevision = apps.get_model('core', 'ConfigRevision')
version = None
revision = None

# Try and get the latest version from cache
try:
version = cache.get('config_version')
except Exception:
pass

# If there is a version in cache, attempt to set revision to the current version from cache
# If the version in cache does not exist or there is no version, try the lastest revision in the database
if not version or (version and not (revision := ConfigRevision.objects.filter(pk=version).first())):
revision = ConfigRevision.objects.order_by('-created').first()

# If there is a revision set, set the active revision
if revision:
revision.active = True
revision.save()


class Migration(migrations.Migration):

dependencies = [
('core', '0018_concrete_objecttype'),
]

operations = [
migrations.AddField(
model_name='configrevision',
name='active',
field=models.BooleanField(default=False),
),
migrations.RunPython(code=get_active, reverse_code=migrations.RunPython.noop),
migrations.AddConstraint(
model_name='configrevision',
constraint=models.UniqueConstraint(
condition=models.Q(('active', True)), fields=('active',), name='unique_active_config_revision'
),
),
]
17 changes: 16 additions & 1 deletion netbox/core/models/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ class ConfigRevision(models.Model):
"""
An atomic revision of NetBox's configuration.
"""
active = models.BooleanField(
default=False
)
created = models.DateTimeField(
verbose_name=_('created'),
auto_now_add=True
Expand All @@ -35,6 +38,13 @@ class Meta:
ordering = ['-created']
verbose_name = _('config revision')
verbose_name_plural = _('config revisions')
constraints = [
models.UniqueConstraint(
fields=('active',),
condition=models.Q(active=True),
name='unique_active_config_revision',
)
]

def __str__(self):
if not self.pk:
Expand All @@ -59,8 +69,13 @@ def activate(self):
"""
cache.set('config', self.data, None)
cache.set('config_version', self.pk, None)

# Set all instances of ConfigRevision to false and set this instance to true
ConfigRevision.objects.all().update(active=False)
ConfigRevision.objects.filter(pk=self.pk).update(active=True)

activate.alters_data = True

@property
def is_active(self):
return cache.get('config_version') == self.pk
return self.active
9 changes: 7 additions & 2 deletions netbox/netbox/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,16 @@ def _populate_from_db(self):
from core.models import ConfigRevision

try:
revision = ConfigRevision.objects.last()
# Enforce the creation date as the ordering parameter
revision = ConfigRevision.objects.get(active=True)
logger.debug(f"Loaded active configuration revision #{revision.pk}")
except (ConfigRevision.DoesNotExist, ConfigRevision.MultipleObjectsReturned):
logger.warning("No active configuration revision found - falling back to most recent")
revision = ConfigRevision.objects.order_by('-created').first()
if revision is None:
logger.debug("No previous configuration found in database; proceeding with default values")
return
logger.debug("Loaded configuration data from database")
logger.debug(f"Using fallback configuration revision #{revision.pk}")
except DatabaseError:
# The database may not be available yet (e.g. when running a management command)
logger.warning("Skipping config initialization (database unavailable)")
Expand Down