diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index 3acafda8b..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', 'local_config_context_data', + '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', 'local_config_context_data', + '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 e5d64ff5d..333e90548 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -823,16 +823,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'}), @@ -920,16 +923,6 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldForm): self.initial['rack'] = self.instance.parent_bay.device.rack_id -class DeviceLocalConfigContextForm(BootstrapMixin, forms.ModelForm): - local_config_context_data = JSONField() - - class Meta: - model = Device - fields = [ - 'local_config_context_data', - ] - - class BaseDeviceCSVForm(forms.ModelForm): device_role = forms.ModelChoiceField( queryset=DeviceRole.objects.all(), diff --git a/netbox/dcim/migrations/0063_device_local_config_context_data.py b/netbox/dcim/migrations/0063_device_local_context_data.py similarity index 90% rename from netbox/dcim/migrations/0063_device_local_config_context_data.py rename to netbox/dcim/migrations/0063_device_local_context_data.py index cbadde2ca..73c568887 100644 --- a/netbox/dcim/migrations/0063_device_local_config_context_data.py +++ b/netbox/dcim/migrations/0063_device_local_context_data.py @@ -13,7 +13,7 @@ class Migration(migrations.Migration): operations = [ migrations.AddField( model_name='device', - name='local_config_context_data', + name='local_context_data', field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True), ), ] diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index bc3677b6e..19c75bdb9 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -1287,10 +1287,6 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel): images = GenericRelation( to='extras.ImageAttachment' ) - local_config_context_data = JSONField( - blank=True, - null=True, - ) objects = DeviceManager() tags = TaggableManager() diff --git a/netbox/dcim/urls.py b/netbox/dcim/urls.py index 51cedaa20..7345cdacd 100644 --- a/netbox/dcim/urls.py +++ b/netbox/dcim/urls.py @@ -142,8 +142,6 @@ urlpatterns = [ url(r'^devices/(?P\d+)/edit/$', views.DeviceEditView.as_view(), name='device_edit'), url(r'^devices/(?P\d+)/delete/$', views.DeviceDeleteView.as_view(), name='device_delete'), url(r'^devices/(?P\d+)/config-context/$', views.DeviceConfigContextView.as_view(), name='device_configcontext'), - url(r'^devices/(?P\d+)/config-context/edit-local/$', views.DeviceEditLocalConfigContextView.as_view(), name='device_edit_localconfigcontext'), - url(r'^devices/(?P\d+)/config-context/clear-local/$', views.DeviceClearLocalContextDataView.as_view(), name='device_delete_localconfigcontext'), url(r'^devices/(?P\d+)/changelog/$', ObjectChangeLogView.as_view(), name='device_changelog', kwargs={'model': Device}), url(r'^devices/(?P\d+)/inventory/$', views.DeviceInventoryView.as_view(), name='device_inventory'), url(r'^devices/(?P\d+)/status/$', views.DeviceStatusView.as_view(), name='device_status'), diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 42106b060..eb7f71a25 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -26,7 +26,7 @@ from utilities.forms import ConfirmationForm from utilities.paginator import EnhancedPaginator from utilities.views import ( BulkComponentCreateView, BulkDeleteView, BulkEditView, BulkImportView, ComponentCreateView, GetReturnURLMixin, - ObjectDeleteView, ObjectEditView, ObjectListView, ObjectSetFieldNullView, + ObjectDeleteView, ObjectEditView, ObjectListView, ) from virtualization.models import VirtualMachine from . import filters, forms, tables @@ -983,19 +983,6 @@ class DeviceEditView(DeviceCreateView): permission_required = 'dcim.change_device' -class DeviceEditLocalConfigContextView(DeviceCreateView): - permission_required = 'dcim.change_device' - model_form = forms.DeviceLocalConfigContextForm - template_name = 'dcim/device_edit_local_config_context.html' - - -class DeviceClearLocalContextDataView(ObjectSetFieldNullView): - permission_required = 'dcim.change_device' - model = Device - field = 'local_config_context_data' - field_human_friendly_name = 'local config context' - - class DeviceDeleteView(PermissionRequiredMixin, ObjectDeleteView): permission_required = 'dcim.delete_device' model = Device diff --git a/netbox/extras/models.py b/netbox/extras/models.py index 467a96f64..2ccb7cdf1 100644 --- a/netbox/extras/models.py +++ b/netbox/extras/models.py @@ -703,6 +703,11 @@ class ConfigContext(models.Model): class ConfigContextModel(models.Model): + local_context_data = JSONField( + blank=True, + null=True, + ) + class Meta: abstract = True @@ -717,8 +722,8 @@ class ConfigContextModel(models.Model): data.update(context.data) # If the object has local config context data defined, that data overwrites all rendered data - if self.local_config_context_data is not None: - data.update(self.local_config_context_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 c49242772..7626d4012 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -107,14 +107,10 @@ 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 - app_label = self.object_class._meta.app_label return render(request, 'extras/object_configcontext.html', { model_name: obj, 'obj': obj, - 'perm_string': '{}.change_{}'.format(app_label, model_name), - 'edit_url': '{}:{}_edit_localconfigcontext'.format(app_label, model_name), - 'delete_url': '{}:{}_delete_localconfigcontext'.format(app_label, model_name), '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 eab3df10b..d23455c19 100644 --- a/netbox/templates/extras/object_configcontext.html +++ b/netbox/templates/extras/object_configcontext.html @@ -21,32 +21,18 @@ Local Context
- {% if obj.local_config_context_data %} -
{{ obj.local_config_context_data|render_json }}
+ {% if obj.local_context_data %} +
{{ obj.local_context_data|render_json }}
{% else %} None {% endif %} +
+ -
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/utilities/views.py b/netbox/utilities/views.py index 5e1dd68df..e11d681ef 100644 --- a/netbox/utilities/views.py +++ b/netbox/utilities/views.py @@ -844,56 +844,6 @@ class BulkComponentCreateView(GetReturnURLMixin, View): }) -class ObjectSetFieldNullView(ObjectDeleteView): - """ - Given a field name, set it to None (null) and save the object. - - field: The field to be nulled - field_friendly_name: Human friendly name for the field in the UI. - """ - template_name = 'utilities/object_set_field_null.html' - field_human_friendly_name = None - - def get(self, request, **kwargs): - - obj = self.get_object(kwargs) - form = ConfirmationForm(initial=request.GET) - - return render(request, self.template_name, { - 'obj': obj, - 'form': form, - 'obj_type': self.model._meta.verbose_name, - 'field_human_friendly_name': self.field_human_friendly_name, - 'return_url': self.get_return_url(request, obj), - }) - - def post(self, request, **kwargs): - - obj = self.get_object(kwargs) - form = ConfirmationForm(request.POST) - if form.is_valid(): - - setattr(obj, self.field, None) - obj.save() - - msg = 'Cleared {} on {} {}'.format(self.field_human_friendly_name, self.model._meta.verbose_name, obj) - messages.success(request, msg) - - return_url = form.cleaned_data.get('return_url') - if return_url is not None and is_safe_url(url=return_url, host=request.get_host()): - return redirect(return_url) - else: - return redirect(self.get_return_url(request, obj)) - - return render(request, self.template_name, { - 'obj': obj, - 'form': form, - 'obj_type': self.model._meta.verbose_name, - 'field_human_friendly_name': self.field_human_friendly_name, - 'return_url': self.get_return_url(request, obj), - }) - - @requires_csrf_token def server_error(request, template_name=ERROR_500_TEMPLATE_NAME): """ diff --git a/netbox/virtualization/api/serializers.py b/netbox/virtualization/api/serializers.py index faa2f3161..80a2f756a 100644 --- a/netbox/virtualization/api/serializers.py +++ b/netbox/virtualization/api/serializers.py @@ -107,7 +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_config_context_data', + 'local_context_data', ] @@ -118,7 +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_config_context_data', + 'local_context_data', ] def get_config_context(self, obj): diff --git a/netbox/virtualization/forms.py b/netbox/virtualization/forms.py index 686864820..9853157d8 100644 --- a/netbox/virtualization/forms.py +++ b/netbox/virtualization/forms.py @@ -248,6 +248,7 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldForm): ) ) tags = TagField(required=False) + local_context_data = JSONField(required=False) class Meta: model = VirtualMachine @@ -255,6 +256,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): @@ -303,16 +307,6 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldForm): self.fields['primary_ip6'].widget.attrs['readonly'] = True -class VirtualMachineLocalConfigContextForm(BootstrapMixin, forms.ModelForm): - local_config_context_data = JSONField() - - class Meta: - model = VirtualMachine - fields = [ - 'local_config_context_data', - ] - - class VirtualMachineCSVForm(forms.ModelForm): status = CSVChoiceField( choices=VM_STATUS_CHOICES, diff --git a/netbox/virtualization/migrations/0008_virtualmachine_local_config_context_data.py b/netbox/virtualization/migrations/0008_virtualmachine_local_context_data.py similarity index 90% rename from netbox/virtualization/migrations/0008_virtualmachine_local_config_context_data.py rename to netbox/virtualization/migrations/0008_virtualmachine_local_context_data.py index e6f4b2bbf..ce8105d95 100644 --- a/netbox/virtualization/migrations/0008_virtualmachine_local_config_context_data.py +++ b/netbox/virtualization/migrations/0008_virtualmachine_local_context_data.py @@ -13,7 +13,7 @@ class Migration(migrations.Migration): operations = [ migrations.AddField( model_name='virtualmachine', - name='local_config_context_data', + 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 9a55c10fd..16c90c1cd 100644 --- a/netbox/virtualization/models.py +++ b/netbox/virtualization/models.py @@ -245,10 +245,6 @@ class VirtualMachine(ChangeLoggedModel, ConfigContextModel, CustomFieldModel): content_type_field='obj_type', object_id_field='obj_id' ) - local_config_context_data = JSONField( - blank=True, - null=True, - ) tags = TaggableManager() diff --git a/netbox/virtualization/urls.py b/netbox/virtualization/urls.py index 0af76bba2..b03b3bc0a 100644 --- a/netbox/virtualization/urls.py +++ b/netbox/virtualization/urls.py @@ -49,8 +49,6 @@ urlpatterns = [ url(r'^virtual-machines/(?P\d+)/edit/$', views.VirtualMachineEditView.as_view(), name='virtualmachine_edit'), url(r'^virtual-machines/(?P\d+)/delete/$', views.VirtualMachineDeleteView.as_view(), name='virtualmachine_delete'), url(r'^virtual-machines/(?P\d+)/config-context/$', views.VirtualMachineConfigContextView.as_view(), name='virtualmachine_configcontext'), - url(r'^virtual-machines/(?P\d+)/config-context/edit-local/$', views.VirtualMachineEditLocalConfigContextView.as_view(), name='virtualmachine_edit_localconfigcontext'), - url(r'^virtual-machines/(?P\d+)/config-context/clear-local/$', views.VirtualMachineClearLocalContextDataView.as_view(), name='virtualmachine_delete_localconfigcontext'), url(r'^virtual-machines/(?P\d+)/changelog/$', ObjectChangeLogView.as_view(), name='virtualmachine_changelog', kwargs={'model': VirtualMachine}), url(r'^virtual-machines/(?P\d+)/services/assign/$', ServiceCreateView.as_view(), name='virtualmachine_service_assign'), diff --git a/netbox/virtualization/views.py b/netbox/virtualization/views.py index 34423f012..d4728da45 100644 --- a/netbox/virtualization/views.py +++ b/netbox/virtualization/views.py @@ -14,7 +14,7 @@ from extras.views import ObjectConfigContextView from ipam.models import Service from utilities.views import ( BulkComponentCreateView, BulkDeleteView, BulkEditView, BulkImportView, ComponentCreateView, ObjectDeleteView, - ObjectEditView, ObjectListView, ObjectSetFieldNullView, + ObjectEditView, ObjectListView, ) from . import filters, forms, tables from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine @@ -285,19 +285,6 @@ class VirtualMachineCreateView(PermissionRequiredMixin, ObjectEditView): default_return_url = 'virtualization:virtualmachine_list' -class VirtualMachineEditLocalConfigContextView(VirtualMachineCreateView): - permission_required = 'virtualization.change_device' - model_form = forms.VirtualMachineLocalConfigContextForm - template_name = 'virtualization/virtualmachine_edit_local_config_context.html' - - -class VirtualMachineClearLocalContextDataView(ObjectSetFieldNullView): - permission_required = 'virtualization.change_virtualmachine' - model = VirtualMachine - field = 'local_config_context_data' - field_human_friendly_name = 'local config context' - - class VirtualMachineEditView(VirtualMachineCreateView): permission_required = 'virtualization.change_virtualmachine'