diff --git a/docs/additional-features/context-data.md b/docs/additional-features/context-data.md index cd9f1ceaa..465b4d2dc 100644 --- a/docs/additional-features/context-data.md +++ b/docs/additional-features/context-data.md @@ -1,3 +1,5 @@ # Contextual Configuration Data Sometimes it is desirable to associate arbitrary data with a group of devices to aid in their configuration. For example, you might want to associate a set of syslog servers for all devices at a particular site. Context data enables the association of arbitrary data to devices and virtual machines grouped by region, site, role, platform, and/or tenant. Context data is arranged hierarchically, so that data with a higher weight can be entered to override more general lower-weight data. Multiple instances of data are automatically merged by NetBox to present a single dictionary for each object. + +Devices and Virtual Machines may also have a local config context defined. This local context will always overwrite the rendered config context objects for the Device/VM. This is useful in situations were the device requires a one-off value different from the rest of the environment. diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index 0478932f7..a95743fd5 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -412,7 +412,7 @@ class DeviceSerializer(TaggitSerializer, CustomFieldModelSerializer): 'id', 'name', 'display_name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag', 'site', 'rack', 'position', 'face', 'parent_device', 'status', 'primary_ip', 'primary_ip4', 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'tags', 'custom_fields', 'created', - 'last_updated', + 'last_updated', 'local_context_data', ] validators = [] @@ -448,7 +448,7 @@ class DeviceWithConfigContextSerializer(DeviceSerializer): 'id', 'name', 'display_name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag', 'site', 'rack', 'position', 'face', 'parent_device', 'status', 'primary_ip', 'primary_ip4', 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'tags', 'custom_fields', - 'config_context', 'created', 'last_updated', + 'config_context', 'created', 'last_updated', 'local_context_data', ] def get_config_context(self, obj): diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 74af0bcbd..cec2fafad 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -18,7 +18,7 @@ from utilities.forms import ( AnnotatedMultipleChoiceField, APISelect, add_blank_choice, ArrayFieldSelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, ChainedFieldsMixin, ChainedModelChoiceField, CommentField, ComponentForm, ConfirmationForm, CSVChoiceField, ExpandableNameField, FilterChoiceField, FilterTreeNodeMultipleChoiceField, - FlexibleModelChoiceField, Livesearch, SelectWithDisabled, SelectWithPK, SmallTextarea, SlugField, + FlexibleModelChoiceField, JSONField, Livesearch, SelectWithDisabled, SelectWithPK, SmallTextarea, SlugField, ) from virtualization.models import Cluster from .constants import ( @@ -822,16 +822,19 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldForm): ) comments = CommentField() tags = TagField(required=False) + local_context_data = JSONField(required=False) class Meta: model = Device fields = [ 'name', 'device_role', 'device_type', 'serial', 'asset_tag', 'site', 'rack', 'position', 'face', 'status', 'platform', 'primary_ip4', 'primary_ip6', 'tenant_group', 'tenant', 'comments', 'tags', + 'local_context_data' ] help_texts = { 'device_role': "The function this device serves", 'serial': "Chassis serial number", + 'local_context_data': "Local config context data overwrites all sources contexts in the final rendered config context" } widgets = { 'face': forms.Select(attrs={'filter-for': 'position'}), diff --git a/netbox/dcim/migrations/0063_device_local_context_data.py b/netbox/dcim/migrations/0063_device_local_context_data.py new file mode 100644 index 000000000..73c568887 --- /dev/null +++ b/netbox/dcim/migrations/0063_device_local_context_data.py @@ -0,0 +1,19 @@ +# Generated by Django 2.0.8 on 2018-09-16 02:01 + +import django.contrib.postgres.fields.jsonb +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('dcim', '0062_interface_mtu'), + ] + + operations = [ + migrations.AddField( + model_name='device', + name='local_context_data', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True), + ), + ] diff --git a/netbox/extras/models.py b/netbox/extras/models.py index cf4d646f3..de3edca9b 100644 --- a/netbox/extras/models.py +++ b/netbox/extras/models.py @@ -711,6 +711,11 @@ class ConfigContext(models.Model): class ConfigContextModel(models.Model): + local_context_data = JSONField( + blank=True, + null=True, + ) + class Meta: abstract = True @@ -724,6 +729,10 @@ class ConfigContextModel(models.Model): for context in ConfigContext.objects.get_for_object(self): data.update(context.data) + # If the object has local config context data defined, that data overwrites all rendered data + if self.local_context_data is not None: + data.update(self.local_context_data) + return data diff --git a/netbox/extras/views.py b/netbox/extras/views.py index 90d0d698d..7626d4012 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -106,9 +106,11 @@ class ObjectConfigContextView(View): obj = get_object_or_404(self.object_class, pk=pk) source_contexts = ConfigContext.objects.get_for_object(obj) + model_name = self.object_class._meta.model_name return render(request, 'extras/object_configcontext.html', { - self.object_class._meta.model_name: obj, + model_name: obj, + 'obj': obj, 'rendered_context': obj.get_config_context(), 'source_contexts': source_contexts, 'base_template': self.base_template, diff --git a/netbox/templates/dcim/device_edit.html b/netbox/templates/dcim/device_edit.html index 23e023c5c..23b2b404e 100644 --- a/netbox/templates/dcim/device_edit.html +++ b/netbox/templates/dcim/device_edit.html @@ -77,6 +77,12 @@ {% endif %} +
+
Local Config Context Data
+
+ {% render_field form.local_context_data %} +
+
Tags
diff --git a/netbox/templates/extras/object_configcontext.html b/netbox/templates/extras/object_configcontext.html index 81f8e1780..d23455c19 100644 --- a/netbox/templates/extras/object_configcontext.html +++ b/netbox/templates/extras/object_configcontext.html @@ -16,6 +16,24 @@
+
+
+ Local Context +
+
+ {% if obj.local_context_data %} +
{{ obj.local_context_data|render_json }}
+ {% else %} + None + {% endif %} +
+ +
Source Contexts diff --git a/netbox/templates/virtualization/virtualmachine_edit.html b/netbox/templates/virtualization/virtualmachine_edit.html index ad49f752d..3be462c4d 100644 --- a/netbox/templates/virtualization/virtualmachine_edit.html +++ b/netbox/templates/virtualization/virtualmachine_edit.html @@ -48,6 +48,12 @@
{% endif %} +
+
Local Config Context Data
+
+ {% render_field form.local_context_data %} +
+
Tags
diff --git a/netbox/virtualization/api/serializers.py b/netbox/virtualization/api/serializers.py index 9dff223d3..80a2f756a 100644 --- a/netbox/virtualization/api/serializers.py +++ b/netbox/virtualization/api/serializers.py @@ -107,6 +107,7 @@ class VirtualMachineSerializer(TaggitSerializer, CustomFieldModelSerializer): fields = [ 'id', 'name', 'status', 'site', 'cluster', 'role', 'tenant', 'platform', 'primary_ip', 'primary_ip4', 'primary_ip6', 'vcpus', 'memory', 'disk', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', + 'local_context_data', ] @@ -117,6 +118,7 @@ class VirtualMachineWithConfigContextSerializer(VirtualMachineSerializer): fields = [ 'id', 'name', 'status', 'cluster', 'role', 'tenant', 'platform', 'primary_ip', 'primary_ip4', 'primary_ip6', 'vcpus', 'memory', 'disk', 'comments', 'tags', 'custom_fields', 'config_context', 'created', 'last_updated', + 'local_context_data', ] def get_config_context(self, obj): diff --git a/netbox/virtualization/forms.py b/netbox/virtualization/forms.py index 478ac9503..ee1007be3 100644 --- a/netbox/virtualization/forms.py +++ b/netbox/virtualization/forms.py @@ -16,7 +16,8 @@ from tenancy.models import Tenant from utilities.forms import ( AnnotatedMultipleChoiceField, APISelect, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, ChainedFieldsMixin, ChainedModelChoiceField, ChainedModelMultipleChoiceField, CommentField, ComponentForm, - ConfirmationForm, CSVChoiceField, ExpandableNameField, FilterChoiceField, SlugField, SmallTextarea, add_blank_choice + ConfirmationForm, CSVChoiceField, ExpandableNameField, FilterChoiceField, JSONField, SlugField, SmallTextarea, + add_blank_choice ) from .constants import VM_STATUS_CHOICES from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine @@ -246,6 +247,7 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldForm): ) ) tags = TagField(required=False) + local_context_data = JSONField(required=False) class Meta: model = VirtualMachine @@ -253,6 +255,9 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldForm): 'name', 'status', 'cluster_group', 'cluster', 'role', 'tenant', 'platform', 'primary_ip4', 'primary_ip6', 'vcpus', 'memory', 'disk', 'comments', 'tags', ] + help_texts = { + 'local_context_data': "Local config context data overwrites all sources contexts in the final rendered config context", + } def __init__(self, *args, **kwargs): diff --git a/netbox/virtualization/migrations/0008_virtualmachine_local_context_data.py b/netbox/virtualization/migrations/0008_virtualmachine_local_context_data.py new file mode 100644 index 000000000..ce8105d95 --- /dev/null +++ b/netbox/virtualization/migrations/0008_virtualmachine_local_context_data.py @@ -0,0 +1,19 @@ +# Generated by Django 2.0.8 on 2018-09-16 02:01 + +import django.contrib.postgres.fields.jsonb +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('virtualization', '0007_change_logging'), + ] + + operations = [ + migrations.AddField( + model_name='virtualmachine', + name='local_context_data', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True), + ), + ] diff --git a/netbox/virtualization/models.py b/netbox/virtualization/models.py index 119c9ee4f..16c90c1cd 100644 --- a/netbox/virtualization/models.py +++ b/netbox/virtualization/models.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals from django.conf import settings from django.contrib.contenttypes.fields import GenericRelation +from django.contrib.postgres.fields import JSONField from django.core.exceptions import ValidationError from django.db import models from django.urls import reverse