mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-17 12:42:52 -06:00
Move ConfigContext classes out of models.py
This commit is contained in:
parent
2bb4a81e23
commit
e97adcb614
@ -1,9 +1,7 @@
|
|||||||
from .change_logging import ObjectChange
|
from .change_logging import ObjectChange
|
||||||
|
from .configcontexts import ConfigContext, ConfigContextModel
|
||||||
from .customfields import CustomField
|
from .customfields import CustomField
|
||||||
from .models import (
|
from .models import CustomLink, ExportTemplate, ImageAttachment, JobResult, Report, Script, Webhook
|
||||||
ConfigContext, ConfigContextModel, CustomLink, ExportTemplate, ImageAttachment, JobResult, Report, Script,
|
|
||||||
Webhook,
|
|
||||||
)
|
|
||||||
from .tags import Tag, TaggedItem
|
from .tags import Tag, TaggedItem
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
|
161
netbox/extras/models/configcontexts.py
Normal file
161
netbox/extras/models/configcontexts.py
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
from django.core.validators import ValidationError
|
||||||
|
from django.db import models
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from extras.querysets import ConfigContextQuerySet
|
||||||
|
from extras.utils import extras_features
|
||||||
|
from netbox.models import ChangeLoggedModel
|
||||||
|
from utilities.utils import deepmerge
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
'ConfigContext',
|
||||||
|
'ConfigContextModel',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Config contexts
|
||||||
|
#
|
||||||
|
|
||||||
|
@extras_features('webhooks')
|
||||||
|
class ConfigContext(ChangeLoggedModel):
|
||||||
|
"""
|
||||||
|
A ConfigContext represents a set of arbitrary data available to any Device or VirtualMachine matching its assigned
|
||||||
|
qualifiers (region, site, etc.). For example, the data stored in a ConfigContext assigned to site A and tenant B
|
||||||
|
will be available to a Device in site A assigned to tenant B. Data is stored in JSON format.
|
||||||
|
"""
|
||||||
|
name = models.CharField(
|
||||||
|
max_length=100,
|
||||||
|
unique=True
|
||||||
|
)
|
||||||
|
weight = models.PositiveSmallIntegerField(
|
||||||
|
default=1000
|
||||||
|
)
|
||||||
|
description = models.CharField(
|
||||||
|
max_length=200,
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
is_active = models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
)
|
||||||
|
regions = models.ManyToManyField(
|
||||||
|
to='dcim.Region',
|
||||||
|
related_name='+',
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
site_groups = models.ManyToManyField(
|
||||||
|
to='dcim.SiteGroup',
|
||||||
|
related_name='+',
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
sites = models.ManyToManyField(
|
||||||
|
to='dcim.Site',
|
||||||
|
related_name='+',
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
roles = models.ManyToManyField(
|
||||||
|
to='dcim.DeviceRole',
|
||||||
|
related_name='+',
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
platforms = models.ManyToManyField(
|
||||||
|
to='dcim.Platform',
|
||||||
|
related_name='+',
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
cluster_groups = models.ManyToManyField(
|
||||||
|
to='virtualization.ClusterGroup',
|
||||||
|
related_name='+',
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
clusters = models.ManyToManyField(
|
||||||
|
to='virtualization.Cluster',
|
||||||
|
related_name='+',
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
tenant_groups = models.ManyToManyField(
|
||||||
|
to='tenancy.TenantGroup',
|
||||||
|
related_name='+',
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
tenants = models.ManyToManyField(
|
||||||
|
to='tenancy.Tenant',
|
||||||
|
related_name='+',
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
tags = models.ManyToManyField(
|
||||||
|
to='extras.Tag',
|
||||||
|
related_name='+',
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
data = models.JSONField()
|
||||||
|
|
||||||
|
objects = ConfigContextQuerySet.as_manager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ['weight', 'name']
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse('extras:configcontext', kwargs={'pk': self.pk})
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
super().clean()
|
||||||
|
|
||||||
|
# Verify that JSON data is provided as an object
|
||||||
|
if type(self.data) is not dict:
|
||||||
|
raise ValidationError(
|
||||||
|
{'data': 'JSON data must be in object form. Example: {"foo": 123}'}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigContextModel(models.Model):
|
||||||
|
"""
|
||||||
|
A model which includes local configuration context data. This local data will override any inherited data from
|
||||||
|
ConfigContexts.
|
||||||
|
"""
|
||||||
|
local_context_data = models.JSONField(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
def get_config_context(self):
|
||||||
|
"""
|
||||||
|
Return the rendered configuration context for a device or VM.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Compile all config data, overwriting lower-weight values with higher-weight values where a collision occurs
|
||||||
|
data = OrderedDict()
|
||||||
|
|
||||||
|
if not hasattr(self, 'config_context_data'):
|
||||||
|
# The annotation is not available, so we fall back to manually querying for the config context objects
|
||||||
|
config_context_data = ConfigContext.objects.get_for_object(self, aggregate_data=True)
|
||||||
|
else:
|
||||||
|
# The attribute may exist, but the annotated value could be None if there is no config context data
|
||||||
|
config_context_data = self.config_context_data or []
|
||||||
|
|
||||||
|
for context in config_context_data:
|
||||||
|
data = deepmerge(data, context)
|
||||||
|
|
||||||
|
# If the object has local config context data defined, merge it last
|
||||||
|
if self.local_context_data:
|
||||||
|
data = deepmerge(data, self.local_context_data)
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
super().clean()
|
||||||
|
|
||||||
|
# Verify that JSON data is provided as an object
|
||||||
|
if self.local_context_data and type(self.local_context_data) is not dict:
|
||||||
|
raise ValidationError(
|
||||||
|
{'local_context_data': 'JSON data must be in object form. Example: {"foo": 123}'}
|
||||||
|
)
|
@ -1,6 +1,5 @@
|
|||||||
import json
|
import json
|
||||||
import uuid
|
import uuid
|
||||||
from collections import OrderedDict
|
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||||
@ -8,22 +7,18 @@ from django.contrib.contenttypes.models import ContentType
|
|||||||
from django.core.validators import ValidationError
|
from django.core.validators import ValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.urls import reverse
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from rest_framework.utils.encoders import JSONEncoder
|
from rest_framework.utils.encoders import JSONEncoder
|
||||||
|
|
||||||
from extras.choices import *
|
from extras.choices import *
|
||||||
from extras.constants import *
|
from extras.constants import *
|
||||||
from extras.querysets import ConfigContextQuerySet
|
|
||||||
from extras.utils import extras_features, FeatureQuery, image_upload
|
from extras.utils import extras_features, FeatureQuery, image_upload
|
||||||
from netbox.models import BigIDModel, ChangeLoggedModel
|
from netbox.models import BigIDModel
|
||||||
from utilities.querysets import RestrictedQuerySet
|
from utilities.querysets import RestrictedQuerySet
|
||||||
from utilities.utils import deepmerge, render_jinja2
|
from utilities.utils import render_jinja2
|
||||||
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'ConfigContext',
|
|
||||||
'ConfigContextModel',
|
|
||||||
'CustomLink',
|
'CustomLink',
|
||||||
'ExportTemplate',
|
'ExportTemplate',
|
||||||
'ImageAttachment',
|
'ImageAttachment',
|
||||||
@ -375,151 +370,6 @@ class ImageAttachment(BigIDModel):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Config contexts
|
|
||||||
#
|
|
||||||
|
|
||||||
@extras_features('webhooks')
|
|
||||||
class ConfigContext(ChangeLoggedModel):
|
|
||||||
"""
|
|
||||||
A ConfigContext represents a set of arbitrary data available to any Device or VirtualMachine matching its assigned
|
|
||||||
qualifiers (region, site, etc.). For example, the data stored in a ConfigContext assigned to site A and tenant B
|
|
||||||
will be available to a Device in site A assigned to tenant B. Data is stored in JSON format.
|
|
||||||
"""
|
|
||||||
name = models.CharField(
|
|
||||||
max_length=100,
|
|
||||||
unique=True
|
|
||||||
)
|
|
||||||
weight = models.PositiveSmallIntegerField(
|
|
||||||
default=1000
|
|
||||||
)
|
|
||||||
description = models.CharField(
|
|
||||||
max_length=200,
|
|
||||||
blank=True
|
|
||||||
)
|
|
||||||
is_active = models.BooleanField(
|
|
||||||
default=True,
|
|
||||||
)
|
|
||||||
regions = models.ManyToManyField(
|
|
||||||
to='dcim.Region',
|
|
||||||
related_name='+',
|
|
||||||
blank=True
|
|
||||||
)
|
|
||||||
site_groups = models.ManyToManyField(
|
|
||||||
to='dcim.SiteGroup',
|
|
||||||
related_name='+',
|
|
||||||
blank=True
|
|
||||||
)
|
|
||||||
sites = models.ManyToManyField(
|
|
||||||
to='dcim.Site',
|
|
||||||
related_name='+',
|
|
||||||
blank=True
|
|
||||||
)
|
|
||||||
roles = models.ManyToManyField(
|
|
||||||
to='dcim.DeviceRole',
|
|
||||||
related_name='+',
|
|
||||||
blank=True
|
|
||||||
)
|
|
||||||
platforms = models.ManyToManyField(
|
|
||||||
to='dcim.Platform',
|
|
||||||
related_name='+',
|
|
||||||
blank=True
|
|
||||||
)
|
|
||||||
cluster_groups = models.ManyToManyField(
|
|
||||||
to='virtualization.ClusterGroup',
|
|
||||||
related_name='+',
|
|
||||||
blank=True
|
|
||||||
)
|
|
||||||
clusters = models.ManyToManyField(
|
|
||||||
to='virtualization.Cluster',
|
|
||||||
related_name='+',
|
|
||||||
blank=True
|
|
||||||
)
|
|
||||||
tenant_groups = models.ManyToManyField(
|
|
||||||
to='tenancy.TenantGroup',
|
|
||||||
related_name='+',
|
|
||||||
blank=True
|
|
||||||
)
|
|
||||||
tenants = models.ManyToManyField(
|
|
||||||
to='tenancy.Tenant',
|
|
||||||
related_name='+',
|
|
||||||
blank=True
|
|
||||||
)
|
|
||||||
tags = models.ManyToManyField(
|
|
||||||
to='extras.Tag',
|
|
||||||
related_name='+',
|
|
||||||
blank=True
|
|
||||||
)
|
|
||||||
data = models.JSONField()
|
|
||||||
|
|
||||||
objects = ConfigContextQuerySet.as_manager()
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
ordering = ['weight', 'name']
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
def get_absolute_url(self):
|
|
||||||
return reverse('extras:configcontext', kwargs={'pk': self.pk})
|
|
||||||
|
|
||||||
def clean(self):
|
|
||||||
super().clean()
|
|
||||||
|
|
||||||
# Verify that JSON data is provided as an object
|
|
||||||
if type(self.data) is not dict:
|
|
||||||
raise ValidationError(
|
|
||||||
{'data': 'JSON data must be in object form. Example: {"foo": 123}'}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigContextModel(models.Model):
|
|
||||||
"""
|
|
||||||
A model which includes local configuration context data. This local data will override any inherited data from
|
|
||||||
ConfigContexts.
|
|
||||||
"""
|
|
||||||
local_context_data = models.JSONField(
|
|
||||||
blank=True,
|
|
||||||
null=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
def get_config_context(self):
|
|
||||||
"""
|
|
||||||
Return the rendered configuration context for a device or VM.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Compile all config data, overwriting lower-weight values with higher-weight values where a collision occurs
|
|
||||||
data = OrderedDict()
|
|
||||||
|
|
||||||
if not hasattr(self, 'config_context_data'):
|
|
||||||
# The annotation is not available, so we fall back to manually querying for the config context objects
|
|
||||||
config_context_data = ConfigContext.objects.get_for_object(self, aggregate_data=True)
|
|
||||||
else:
|
|
||||||
# The attribute may exist, but the annotated value could be None if there is no config context data
|
|
||||||
config_context_data = self.config_context_data or []
|
|
||||||
|
|
||||||
for context in config_context_data:
|
|
||||||
data = deepmerge(data, context)
|
|
||||||
|
|
||||||
# If the object has local config context data defined, merge it last
|
|
||||||
if self.local_context_data:
|
|
||||||
data = deepmerge(data, self.local_context_data)
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
def clean(self):
|
|
||||||
super().clean()
|
|
||||||
|
|
||||||
# Verify that JSON data is provided as an object
|
|
||||||
if self.local_context_data and type(self.local_context_data) is not dict:
|
|
||||||
raise ValidationError(
|
|
||||||
{'local_context_data': 'JSON data must be in object form. Example: {"foo": 123}'}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Custom scripts
|
# Custom scripts
|
||||||
#
|
#
|
||||||
|
Loading…
Reference in New Issue
Block a user