mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-23 17:08:41 -06:00
Initial work on config contexts
This commit is contained in:
parent
ffcbc54522
commit
c13e4858d7
@ -3,7 +3,6 @@ from __future__ import unicode_literals
|
|||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import transaction
|
|
||||||
from django.http import HttpResponseBadRequest, HttpResponseForbidden
|
from django.http import HttpResponseBadRequest, HttpResponseForbidden
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from drf_yasg import openapi
|
from drf_yasg import openapi
|
||||||
@ -233,6 +232,11 @@ class DeviceViewSet(CustomFieldModelViewSet):
|
|||||||
serializer_class = serializers.DeviceSerializer
|
serializer_class = serializers.DeviceSerializer
|
||||||
filter_class = filters.DeviceFilter
|
filter_class = filters.DeviceFilter
|
||||||
|
|
||||||
|
@detail_route(url_path='config-context')
|
||||||
|
def config_context(self, request, pk):
|
||||||
|
device = get_object_or_404(Device, pk=pk)
|
||||||
|
return Response(device.get_config_context())
|
||||||
|
|
||||||
@detail_route(url_path='napalm')
|
@detail_route(url_path='napalm')
|
||||||
def napalm(self, request, pk):
|
def napalm(self, request, pk):
|
||||||
"""
|
"""
|
||||||
|
@ -19,7 +19,7 @@ from timezone_field import TimeZoneField
|
|||||||
|
|
||||||
from circuits.models import Circuit
|
from circuits.models import Circuit
|
||||||
from extras.constants import OBJECTCHANGE_ACTION_DELETE, OBJECTCHANGE_ACTION_UPDATE
|
from extras.constants import OBJECTCHANGE_ACTION_DELETE, OBJECTCHANGE_ACTION_UPDATE
|
||||||
from extras.models import CustomFieldModel, ObjectChange
|
from extras.models import ConfigContextModel, CustomFieldModel, ObjectChange
|
||||||
from extras.rpc import RPC_CLIENTS
|
from extras.rpc import RPC_CLIENTS
|
||||||
from utilities.fields import ColorField, NullableCharField
|
from utilities.fields import ColorField, NullableCharField
|
||||||
from utilities.managers import NaturalOrderByManager
|
from utilities.managers import NaturalOrderByManager
|
||||||
@ -1158,7 +1158,7 @@ class DeviceManager(NaturalOrderByManager):
|
|||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class Device(ChangeLoggedModel, CustomFieldModel):
|
class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
|
||||||
"""
|
"""
|
||||||
A Device represents a piece of physical hardware mounted within a Rack. Each Device is assigned a DeviceType,
|
A Device represents a piece of physical hardware mounted within a Rack. Each Device is assigned a DeviceType,
|
||||||
DeviceRole, and (optionally) a Platform. Device names are not required, however if one is set it must be unique.
|
DeviceRole, and (optionally) a Platform. Device names are not required, however if one is set it must be unique.
|
||||||
|
@ -141,6 +141,7 @@ urlpatterns = [
|
|||||||
url(r'^devices/(?P<pk>\d+)/$', views.DeviceView.as_view(), name='device'),
|
url(r'^devices/(?P<pk>\d+)/$', views.DeviceView.as_view(), name='device'),
|
||||||
url(r'^devices/(?P<pk>\d+)/edit/$', views.DeviceEditView.as_view(), name='device_edit'),
|
url(r'^devices/(?P<pk>\d+)/edit/$', views.DeviceEditView.as_view(), name='device_edit'),
|
||||||
url(r'^devices/(?P<pk>\d+)/delete/$', views.DeviceDeleteView.as_view(), name='device_delete'),
|
url(r'^devices/(?P<pk>\d+)/delete/$', views.DeviceDeleteView.as_view(), name='device_delete'),
|
||||||
|
url(r'^devices/(?P<pk>\d+)/config-context/$', views.DeviceConfigContextView.as_view(), name='device_configcontext'),
|
||||||
url(r'^devices/(?P<pk>\d+)/changelog/$', ObjectChangeLogView.as_view(), name='device_changelog', kwargs={'model': Device}),
|
url(r'^devices/(?P<pk>\d+)/changelog/$', ObjectChangeLogView.as_view(), name='device_changelog', kwargs={'model': Device}),
|
||||||
url(r'^devices/(?P<pk>\d+)/inventory/$', views.DeviceInventoryView.as_view(), name='device_inventory'),
|
url(r'^devices/(?P<pk>\d+)/inventory/$', views.DeviceInventoryView.as_view(), name='device_inventory'),
|
||||||
url(r'^devices/(?P<pk>\d+)/status/$', views.DeviceStatusView.as_view(), name='device_status'),
|
url(r'^devices/(?P<pk>\d+)/status/$', views.DeviceStatusView.as_view(), name='device_status'),
|
||||||
|
@ -994,6 +994,18 @@ class DeviceConfigView(PermissionRequiredMixin, View):
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceConfigContextView(View):
|
||||||
|
|
||||||
|
def get(self, request, pk):
|
||||||
|
|
||||||
|
device = get_object_or_404(Device, pk=pk)
|
||||||
|
|
||||||
|
return render(request, 'dcim/device_configcontext.html', {
|
||||||
|
'device': device,
|
||||||
|
'active_tab': 'config-context',
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
class DeviceCreateView(PermissionRequiredMixin, ObjectEditView):
|
class DeviceCreateView(PermissionRequiredMixin, ObjectEditView):
|
||||||
permission_required = 'dcim.add_device'
|
permission_required = 'dcim.add_device'
|
||||||
model = Device
|
model = Device
|
||||||
|
@ -7,7 +7,8 @@ from django.utils.safestring import mark_safe
|
|||||||
from utilities.forms import LaxURLField
|
from utilities.forms import LaxURLField
|
||||||
from .constants import OBJECTCHANGE_ACTION_CREATE, OBJECTCHANGE_ACTION_DELETE, OBJECTCHANGE_ACTION_UPDATE
|
from .constants import OBJECTCHANGE_ACTION_CREATE, OBJECTCHANGE_ACTION_DELETE, OBJECTCHANGE_ACTION_UPDATE
|
||||||
from .models import (
|
from .models import (
|
||||||
CustomField, CustomFieldChoice, Graph, ExportTemplate, ObjectChange, TopologyMap, UserAction, Webhook,
|
ConfigContext, CustomField, CustomFieldChoice, Graph, ExportTemplate, ObjectChange, TopologyMap, UserAction,
|
||||||
|
Webhook,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -125,6 +126,15 @@ class TopologyMapAdmin(admin.ModelAdmin):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Config contexts
|
||||||
|
#
|
||||||
|
|
||||||
|
@admin.register(ConfigContext)
|
||||||
|
class ConfigContextAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ['name', 'weight']
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Change logging
|
# Change logging
|
||||||
#
|
#
|
||||||
|
@ -4,10 +4,16 @@ from django.core.exceptions import ObjectDoesNotExist
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from taggit.models import Tag
|
from taggit.models import Tag
|
||||||
|
|
||||||
from dcim.api.serializers import NestedDeviceSerializer, NestedRackSerializer, NestedSiteSerializer
|
from dcim.api.serializers import (
|
||||||
|
NestedDeviceSerializer, NestedDeviceRoleSerializer, NestedPlatformSerializer, NestedRackSerializer,
|
||||||
|
NestedRegionSerializer, NestedSiteSerializer,
|
||||||
|
)
|
||||||
from dcim.models import Device, Rack, Site
|
from dcim.models import Device, Rack, Site
|
||||||
from extras.models import ExportTemplate, Graph, ImageAttachment, ObjectChange, ReportResult, TopologyMap, UserAction
|
from extras.models import (
|
||||||
|
ConfigContext, ExportTemplate, Graph, ImageAttachment, ObjectChange, ReportResult, TopologyMap, UserAction,
|
||||||
|
)
|
||||||
from extras.constants import *
|
from extras.constants import *
|
||||||
|
from tenancy.api.serializers import NestedTenantSerializer
|
||||||
from users.api.serializers import NestedUserSerializer
|
from users.api.serializers import NestedUserSerializer
|
||||||
from utilities.api import (
|
from utilities.api import (
|
||||||
ChoiceFieldSerializer, ContentTypeFieldSerializer, get_serializer_for_model, ValidatedModelSerializer,
|
ChoiceFieldSerializer, ContentTypeFieldSerializer, get_serializer_for_model, ValidatedModelSerializer,
|
||||||
@ -121,6 +127,22 @@ class ImageAttachmentSerializer(ValidatedModelSerializer):
|
|||||||
return serializer(obj.parent, context={'request': self.context['request']}).data
|
return serializer(obj.parent, context={'request': self.context['request']}).data
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Config contexts
|
||||||
|
#
|
||||||
|
|
||||||
|
class ConfigContextSerializer(ValidatedModelSerializer):
|
||||||
|
regions = NestedRegionSerializer(many=True)
|
||||||
|
sites = NestedSiteSerializer(many=True)
|
||||||
|
roles = NestedDeviceRoleSerializer(many=True)
|
||||||
|
platforms = NestedPlatformSerializer(many=True)
|
||||||
|
tenants = NestedTenantSerializer(many=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ConfigContext
|
||||||
|
fields = ['name', 'weight', 'is_active', 'regions', 'sites', 'roles', 'platforms', 'tenants', 'data']
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Reports
|
# Reports
|
||||||
#
|
#
|
||||||
|
@ -34,6 +34,9 @@ router.register(r'tags', views.TagViewSet)
|
|||||||
# Image attachments
|
# Image attachments
|
||||||
router.register(r'image-attachments', views.ImageAttachmentViewSet)
|
router.register(r'image-attachments', views.ImageAttachmentViewSet)
|
||||||
|
|
||||||
|
# Config contexts
|
||||||
|
router.register(r'config-contexts', views.ConfigContextViewSet)
|
||||||
|
|
||||||
# Reports
|
# Reports
|
||||||
router.register(r'reports', views.ReportViewSet, base_name='report')
|
router.register(r'reports', views.ReportViewSet, base_name='report')
|
||||||
|
|
||||||
|
@ -12,7 +12,8 @@ from taggit.models import Tag
|
|||||||
|
|
||||||
from extras import filters
|
from extras import filters
|
||||||
from extras.models import (
|
from extras.models import (
|
||||||
CustomField, ExportTemplate, Graph, ImageAttachment, ObjectChange, ReportResult, TopologyMap, UserAction,
|
ConfigContext, CustomField, ExportTemplate, Graph, ImageAttachment, ObjectChange, ReportResult, TopologyMap,
|
||||||
|
UserAction,
|
||||||
)
|
)
|
||||||
from extras.reports import get_report, get_reports
|
from extras.reports import get_report, get_reports
|
||||||
from utilities.api import FieldChoicesViewSet, IsAuthenticatedOrLoginNotRequired, ModelViewSet
|
from utilities.api import FieldChoicesViewSet, IsAuthenticatedOrLoginNotRequired, ModelViewSet
|
||||||
@ -132,6 +133,15 @@ class ImageAttachmentViewSet(ModelViewSet):
|
|||||||
serializer_class = serializers.ImageAttachmentSerializer
|
serializer_class = serializers.ImageAttachmentSerializer
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Config contexts
|
||||||
|
#
|
||||||
|
|
||||||
|
class ConfigContextViewSet(ModelViewSet):
|
||||||
|
queryset = ConfigContext.objects.prefetch_related('regions', 'sites', 'roles', 'platforms', 'tenants')
|
||||||
|
serializer_class = serializers.ConfigContextSerializer
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Reports
|
# Reports
|
||||||
#
|
#
|
||||||
|
@ -5,14 +5,16 @@ from collections import OrderedDict
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from mptt.forms import TreeNodeMultipleChoiceField
|
||||||
from taggit.models import Tag
|
from taggit.models import Tag
|
||||||
|
|
||||||
|
from dcim.models import Region
|
||||||
from utilities.forms import add_blank_choice, BootstrapMixin, BulkEditForm, LaxURLField, SlugField
|
from utilities.forms import add_blank_choice, BootstrapMixin, BulkEditForm, LaxURLField, SlugField
|
||||||
from .constants import (
|
from .constants import (
|
||||||
CF_FILTER_DISABLED, CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_INTEGER, CF_TYPE_SELECT, CF_TYPE_URL,
|
CF_FILTER_DISABLED, CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_INTEGER, CF_TYPE_SELECT, CF_TYPE_URL,
|
||||||
OBJECTCHANGE_ACTION_CHOICES,
|
OBJECTCHANGE_ACTION_CHOICES,
|
||||||
)
|
)
|
||||||
from .models import CustomField, CustomFieldValue, ImageAttachment, ObjectChange
|
from .models import ConfigContext, CustomField, CustomFieldValue, ImageAttachment, ObjectChange
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -174,7 +176,6 @@ class CustomFieldFilterForm(forms.Form):
|
|||||||
#
|
#
|
||||||
# Tags
|
# Tags
|
||||||
#
|
#
|
||||||
#
|
|
||||||
|
|
||||||
class TagForm(BootstrapMixin, forms.ModelForm):
|
class TagForm(BootstrapMixin, forms.ModelForm):
|
||||||
slug = SlugField()
|
slug = SlugField()
|
||||||
@ -184,6 +185,21 @@ class TagForm(BootstrapMixin, forms.ModelForm):
|
|||||||
fields = ['name', 'slug']
|
fields = ['name', 'slug']
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Config contexts
|
||||||
|
#
|
||||||
|
|
||||||
|
class ConfigContextForm(BootstrapMixin, forms.ModelForm):
|
||||||
|
regions = TreeNodeMultipleChoiceField(
|
||||||
|
queryset=Region.objects.all(),
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ConfigContext
|
||||||
|
fields = ['name', 'weight', 'is_active', 'regions', 'sites', 'roles', 'platforms', 'tenants', 'data']
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Image attachments
|
# Image attachments
|
||||||
#
|
#
|
||||||
|
44
netbox/extras/migrations/0014_config-contexts.py
Normal file
44
netbox/extras/migrations/0014_config-contexts.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
# Generated by Django 2.0.6 on 2018-06-27 17:45
|
||||||
|
|
||||||
|
import django.contrib.postgres.fields.jsonb
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('tenancy', '0005_change_logging'),
|
||||||
|
('dcim', '0060_change_logging'),
|
||||||
|
('extras', '0013_objectchange'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ConfigContext',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=100, unique=True)),
|
||||||
|
('weight', models.PositiveSmallIntegerField(default=1000)),
|
||||||
|
('is_active', models.BooleanField(default=True)),
|
||||||
|
('data', django.contrib.postgres.fields.jsonb.JSONField()),
|
||||||
|
('platforms', models.ManyToManyField(blank=True, related_name='_configcontext_platforms_+', to='dcim.Platform')),
|
||||||
|
('regions', models.ManyToManyField(blank=True, related_name='_configcontext_regions_+', to='dcim.Region')),
|
||||||
|
('roles', models.ManyToManyField(blank=True, related_name='_configcontext_roles_+', to='dcim.DeviceRole')),
|
||||||
|
('sites', models.ManyToManyField(blank=True, related_name='_configcontext_sites_+', to='dcim.Site')),
|
||||||
|
('tenants', models.ManyToManyField(blank=True, related_name='_configcontext_tenants_+', to='tenancy.Tenant')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'ordering': ['weight', 'name'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='customfield',
|
||||||
|
name='obj_type',
|
||||||
|
field=models.ManyToManyField(help_text='The object(s) to which this field applies.', limit_choices_to={'model__in': ('provider', 'circuit', 'site', 'rack', 'devicetype', 'device', 'aggregate', 'prefix', 'ipaddress', 'vlan', 'vrf', 'service', 'tenant', 'cluster', 'virtualmachine')}, related_name='custom_fields', to='contenttypes.ContentType', verbose_name='Object(s)'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='webhook',
|
||||||
|
name='obj_type',
|
||||||
|
field=models.ManyToManyField(help_text='The object(s) to which this Webhook applies.', limit_choices_to={'model__in': ('provider', 'circuit', 'site', 'rack', 'rackgroup', 'device', 'interface', 'aggregate', 'prefix', 'ipaddress', 'vlan', 'vlangroup', 'vrf', 'service', 'tenant', 'tenantgroup', 'cluster', 'clustergroup', 'virtualmachine')}, related_name='webhooks', to='contenttypes.ContentType', verbose_name='Object types'),
|
||||||
|
),
|
||||||
|
]
|
@ -629,6 +629,94 @@ class ImageAttachment(models.Model):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Config contexts
|
||||||
|
#
|
||||||
|
|
||||||
|
class ConfigContext(models.Model):
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
)
|
||||||
|
is_active = models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
)
|
||||||
|
regions = models.ManyToManyField(
|
||||||
|
to='dcim.Region',
|
||||||
|
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
|
||||||
|
)
|
||||||
|
tenants = models.ManyToManyField(
|
||||||
|
to='tenancy.Tenant',
|
||||||
|
related_name='+',
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
data = JSONField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ['weight', 'name']
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse('extras:configcontext', kwargs={'pk': self.pk})
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigContextModel(models.Model):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
def get_config_context(self):
|
||||||
|
"""
|
||||||
|
Return the rendered configuration context for a device or VM.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# `device_role` for Device; `role` for VirtualMachine
|
||||||
|
role = getattr(self, 'device_role', None) or self.role
|
||||||
|
|
||||||
|
# Gather all ConfigContexts orders by weight, name
|
||||||
|
contexts = ConfigContext.objects.filter(
|
||||||
|
Q(regions=self.site.region) | Q(regions=None),
|
||||||
|
Q(sites=self.site) | Q(sites=None),
|
||||||
|
Q(roles=role) | Q(roles=None),
|
||||||
|
Q(tenants=self.tenant) | Q(tenants=None),
|
||||||
|
Q(platforms=self.platform) | Q(platforms=None),
|
||||||
|
is_active=True,
|
||||||
|
).order_by('weight', 'name')
|
||||||
|
|
||||||
|
# Compile all config data, overwriting lower-weight values with higher-weight values where a collision occurs
|
||||||
|
data = {}
|
||||||
|
for context in contexts:
|
||||||
|
data.update(context.data)
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Report results
|
# Report results
|
||||||
#
|
#
|
||||||
|
@ -4,7 +4,7 @@ import django_tables2 as tables
|
|||||||
from taggit.models import Tag
|
from taggit.models import Tag
|
||||||
|
|
||||||
from utilities.tables import BaseTable, ToggleColumn
|
from utilities.tables import BaseTable, ToggleColumn
|
||||||
from .models import ObjectChange
|
from .models import ConfigContext, ObjectChange
|
||||||
|
|
||||||
TAG_ACTIONS = """
|
TAG_ACTIONS = """
|
||||||
{% if perms.taggit.change_tag %}
|
{% if perms.taggit.change_tag %}
|
||||||
@ -15,6 +15,15 @@ TAG_ACTIONS = """
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
CONFIGCONTEXT_ACTIONS = """
|
||||||
|
{% if perms.extras.change_configcontext %}
|
||||||
|
<a href="{% url 'extras:configcontext_edit' pk=record.pk %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||||
|
{% endif %}
|
||||||
|
{% if perms.extras.delete_configcontext %}
|
||||||
|
<a href="{% url 'extras:configcontext_delete' pk=record.pk %}" class="btn btn-xs btn-danger"><i class="glyphicon glyphicon-trash" aria-hidden="true"></i></a>
|
||||||
|
{% endif %}
|
||||||
|
"""
|
||||||
|
|
||||||
OBJECTCHANGE_ACTION = """
|
OBJECTCHANGE_ACTION = """
|
||||||
{% if record.action == 1 %}
|
{% if record.action == 1 %}
|
||||||
<span class="label label-success">Created</span>
|
<span class="label label-success">Created</span>
|
||||||
@ -44,7 +53,21 @@ class TagTable(BaseTable):
|
|||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = Tag
|
model = Tag
|
||||||
fields = ('pk', 'name', 'items')
|
fields = ('pk', 'name', 'weight')
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigContextTable(BaseTable):
|
||||||
|
pk = ToggleColumn()
|
||||||
|
name = tables.LinkColumn()
|
||||||
|
actions = tables.TemplateColumn(
|
||||||
|
template_code=CONFIGCONTEXT_ACTIONS,
|
||||||
|
attrs={'td': {'class': 'text-right'}},
|
||||||
|
verbose_name=''
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta(BaseTable.Meta):
|
||||||
|
model = ConfigContext
|
||||||
|
fields = ('pk', 'name', 'weight', 'active')
|
||||||
|
|
||||||
|
|
||||||
class ObjectChangeTable(BaseTable):
|
class ObjectChangeTable(BaseTable):
|
||||||
|
@ -13,6 +13,14 @@ urlpatterns = [
|
|||||||
url(r'^tags/(?P<slug>[\w-]+)/delete/$', views.TagDeleteView.as_view(), name='tag_delete'),
|
url(r'^tags/(?P<slug>[\w-]+)/delete/$', views.TagDeleteView.as_view(), name='tag_delete'),
|
||||||
url(r'^tags/delete/$', views.TagBulkDeleteView.as_view(), name='tag_bulk_delete'),
|
url(r'^tags/delete/$', views.TagBulkDeleteView.as_view(), name='tag_bulk_delete'),
|
||||||
|
|
||||||
|
# Config contexts
|
||||||
|
url(r'^config-contexts/$', views.ConfigContextListView.as_view(), name='configcontext_list'),
|
||||||
|
url(r'^config-contexts/add/$', views.ConfigContextCreateView.as_view(), name='configcontext_add'),
|
||||||
|
url(r'^config-contexts/(?P<pk>\d+)/$', views.ConfigContextView.as_view(), name='configcontext'),
|
||||||
|
url(r'^config-contexts/(?P<pk>\d+)/edit/$', views.ConfigContextEditView.as_view(), name='configcontext_edit'),
|
||||||
|
url(r'^config-contexts/(?P<pk>\d+)/delete/$', views.ConfigContextDeleteView.as_view(), name='configcontext_delete'),
|
||||||
|
url(r'^config-contexts/delete/$', views.ConfigContextBulkDeleteView.as_view(), name='configcontext_bulk_delete'),
|
||||||
|
|
||||||
# Image attachments
|
# Image attachments
|
||||||
url(r'^image-attachments/(?P<pk>\d+)/edit/$', views.ImageAttachmentEditView.as_view(), name='imageattachment_edit'),
|
url(r'^image-attachments/(?P<pk>\d+)/edit/$', views.ImageAttachmentEditView.as_view(), name='imageattachment_edit'),
|
||||||
url(r'^image-attachments/(?P<pk>\d+)/delete/$', views.ImageAttachmentDeleteView.as_view(), name='imageattachment_delete'),
|
url(r'^image-attachments/(?P<pk>\d+)/delete/$', views.ImageAttachmentDeleteView.as_view(), name='imageattachment_delete'),
|
||||||
|
@ -14,10 +14,10 @@ from taggit.models import Tag
|
|||||||
from utilities.forms import ConfirmationForm
|
from utilities.forms import ConfirmationForm
|
||||||
from utilities.views import BulkDeleteView, ObjectDeleteView, ObjectEditView, ObjectListView
|
from utilities.views import BulkDeleteView, ObjectDeleteView, ObjectEditView, ObjectListView
|
||||||
from . import filters
|
from . import filters
|
||||||
from .forms import ObjectChangeFilterForm, ImageAttachmentForm, TagForm
|
from .forms import ConfigContextForm, ImageAttachmentForm, ObjectChangeFilterForm, TagForm
|
||||||
from .models import ImageAttachment, ObjectChange, ReportResult
|
from .models import ConfigContext, ImageAttachment, ObjectChange, ReportResult
|
||||||
from .reports import get_report, get_reports
|
from .reports import get_report, get_reports
|
||||||
from .tables import ObjectChangeTable, TagTable
|
from .tables import ConfigContextTable, ObjectChangeTable, TagTable
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -53,6 +53,53 @@ class TagBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
|||||||
default_return_url = 'extras:tag_list'
|
default_return_url = 'extras:tag_list'
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Config contexts
|
||||||
|
#
|
||||||
|
|
||||||
|
class ConfigContextListView(ObjectListView):
|
||||||
|
queryset = ConfigContext.objects.all()
|
||||||
|
table = ConfigContextTable
|
||||||
|
template_name = 'extras/configcontext_list.html'
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigContextView(View):
|
||||||
|
|
||||||
|
def get(self, request, pk):
|
||||||
|
|
||||||
|
configcontext = get_object_or_404(ConfigContext, pk=pk)
|
||||||
|
|
||||||
|
return render(request, 'extras/configcontext.html', {
|
||||||
|
'configcontext': configcontext,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigContextCreateView(PermissionRequiredMixin, ObjectEditView):
|
||||||
|
permission_required = 'extras.add_configcontext'
|
||||||
|
model = ConfigContext
|
||||||
|
model_form = ConfigContextForm
|
||||||
|
default_return_url = 'extras:configcontext_list'
|
||||||
|
template_name = 'extras/configcontext_edit.html'
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigContextEditView(ConfigContextCreateView):
|
||||||
|
permission_required = 'extras.change_configcontext'
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigContextDeleteView(PermissionRequiredMixin, ObjectDeleteView):
|
||||||
|
permission_required = 'extras.delete_configcontext'
|
||||||
|
model = ConfigContext
|
||||||
|
default_return_url = 'extras:configcontext_list'
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigContextBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
|
permission_required = 'extras.delete_cconfigcontext'
|
||||||
|
cls = ConfigContext
|
||||||
|
queryset = ConfigContext.objects.all()
|
||||||
|
table = ConfigContextTable
|
||||||
|
default_return_url = 'extras:configcontext_list'
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Change logging
|
# Change logging
|
||||||
#
|
#
|
||||||
|
@ -69,6 +69,9 @@
|
|||||||
{% include 'dcim/inc/device_napalm_tabs.html' %}
|
{% include 'dcim/inc/device_napalm_tabs.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<li role="presentation"{% if active_tab == 'config-context' %} class="active"{% endif %}>
|
||||||
|
<a href="{% url 'dcim:device_configcontext' pk=device.pk %}">Config Context</a>
|
||||||
|
</li>
|
||||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||||
<a href="{% url 'dcim:device_changelog' pk=device.pk %}">Changelog</a>
|
<a href="{% url 'dcim:device_changelog' pk=device.pk %}">Changelog</a>
|
||||||
</li>
|
</li>
|
||||||
|
18
netbox/templates/dcim/device_configcontext.html
Normal file
18
netbox/templates/dcim/device_configcontext.html
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{% extends 'dcim/device.html' %}
|
||||||
|
|
||||||
|
{% block title %}{{ device }} - Config Context{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<strong>Config Context</strong>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<pre>{{ device.get_config_context }}</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
154
netbox/templates/extras/configcontext.html
Normal file
154
netbox/templates/extras/configcontext.html
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
{% extends '_base.html' %}
|
||||||
|
{% load helpers %}
|
||||||
|
|
||||||
|
{% block header %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-8 col-md-9">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li><a href="{% url 'extras:configcontext_list' %}">Config Contexts</a></li>
|
||||||
|
<li>{{ configcontext }}</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-4 col-md-3">
|
||||||
|
<form action="{% url 'extras:configcontext_list' %}" method="get">
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" name="q" class="form-control" />
|
||||||
|
<span class="input-group-btn">
|
||||||
|
<button type="submit" class="btn btn-primary">
|
||||||
|
<span class="fa fa-search" aria-hidden="true"></span>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="pull-right">
|
||||||
|
{% if perms.extras.change_configcontext %}
|
||||||
|
<a href="{% url 'extras:configcontext_edit' pk=configcontext.pk %}" class="btn btn-warning">
|
||||||
|
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
|
||||||
|
Edit this config context
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<h1>{% block title %}{{ configcontext }}{% endblock %}</h1>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-5">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<strong>Config Context</strong>
|
||||||
|
</div>
|
||||||
|
<table class="table table-hover panel-body attr-table">
|
||||||
|
<tr>
|
||||||
|
<td>Name</td>
|
||||||
|
<td>
|
||||||
|
{{ configcontext.name }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Weight</td>
|
||||||
|
<td>
|
||||||
|
{{ configcontext.weight }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Active</td>
|
||||||
|
<td>
|
||||||
|
{% if configcontext.is_active %}
|
||||||
|
<span class="text-success">
|
||||||
|
<i class="fa fa-check"></i>
|
||||||
|
</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="text-danger">
|
||||||
|
<i class="fa fa-close"></i>
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Regions</td>
|
||||||
|
<td>
|
||||||
|
{% if configcontext.regions.all %}
|
||||||
|
<ul>
|
||||||
|
{% for region in configcontext.regions.all %}
|
||||||
|
<li><a href="{{ region.get_absolute_url }}">{{ region }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% else %}
|
||||||
|
<span class="text-muted">None</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Sites</td>
|
||||||
|
<td>
|
||||||
|
{% if configcontext.sites.all %}
|
||||||
|
<ul>
|
||||||
|
{% for site in configcontext.sites.all %}
|
||||||
|
<li><a href="{{ site.get_absolute_url }}">{{ site }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% else %}
|
||||||
|
<span class="text-muted">None</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Roles</td>
|
||||||
|
<td>
|
||||||
|
{% if configcontext.roles.all %}
|
||||||
|
<ul>
|
||||||
|
{% for role in configcontext.roles.all %}
|
||||||
|
<li><a href="{{ role.get_absolute_url }}">{{ role }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% else %}
|
||||||
|
<span class="text-muted">None</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Platforms</td>
|
||||||
|
<td>
|
||||||
|
{% if configcontext.platforms.all %}
|
||||||
|
<ul>
|
||||||
|
{% for platform in configcontext.platforms.all %}
|
||||||
|
<li><a href="{{ platform.get_absolute_url }}">{{ platform }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% else %}
|
||||||
|
<span class="text-muted">None</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Tenants</td>
|
||||||
|
<td>
|
||||||
|
{% if configcontext.tenants.all %}
|
||||||
|
<ul>
|
||||||
|
{% for tenant in configcontext.tenants.all %}
|
||||||
|
<li><a href="{{ tenant.get_absolute_url }}">{{ tenant }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% else %}
|
||||||
|
<span class="text-muted">None</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-7">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<strong>Data</strong>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<pre>{{ configcontext.data }}</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
24
netbox/templates/extras/configcontext_edit.html
Normal file
24
netbox/templates/extras/configcontext_edit.html
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{% extends 'utilities/obj_edit.html' %}
|
||||||
|
{% load form_helpers %}
|
||||||
|
|
||||||
|
{% block form %}
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading"><strong>Config Context</strong></div>
|
||||||
|
<div class="panel-body">
|
||||||
|
{% render_field form.name %}
|
||||||
|
{% render_field form.weight %}
|
||||||
|
{% render_field form.is_active %}
|
||||||
|
{% render_field form.regions %}
|
||||||
|
{% render_field form.sites %}
|
||||||
|
{% render_field form.roles %}
|
||||||
|
{% render_field form.platforms %}
|
||||||
|
{% render_field form.tenants %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading"><strong>Data</strong></div>
|
||||||
|
<div class="panel-body">
|
||||||
|
{% render_field form.data %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
16
netbox/templates/extras/configcontext_list.html
Normal file
16
netbox/templates/extras/configcontext_list.html
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{% extends '_base.html' %}
|
||||||
|
{% load buttons %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="pull-right">
|
||||||
|
{% if perms.extras.add_configcontext %}
|
||||||
|
{% add_button 'extras:configcontext_add' %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<h1>{% block title %}Config Contexts{% endblock %}</h1>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
{% include 'utilities/obj_table.html' with bulk_delete_url='extras:configcontext_bulk_delete' %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
@ -63,6 +63,9 @@
|
|||||||
<li>
|
<li>
|
||||||
<a href="{% url 'extras:tag_list' %}">Tags</a>
|
<a href="{% url 'extras:tag_list' %}">Tags</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{% url 'extras:configcontext_list' %}">Config Contexts</a>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url 'extras:report_list' %}">Reports</a>
|
<a href="{% url 'extras:report_list' %}">Reports</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -44,6 +44,9 @@
|
|||||||
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
|
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
|
||||||
<a href="{{ virtualmachine.get_absolute_url }}">Virtual Machine</a>
|
<a href="{{ virtualmachine.get_absolute_url }}">Virtual Machine</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li role="presentation"{% if active_tab == 'config-context' %} class="active"{% endif %}>
|
||||||
|
<a href="{% url 'virtualization:virtualmachine_configcontext' pk=virtualmachine.pk %}">Config Context</a>
|
||||||
|
</li>
|
||||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||||
<a href="{% url 'virtualization:virtualmachine_changelog' pk=virtualmachine.pk %}">Changelog</a>
|
<a href="{% url 'virtualization:virtualmachine_changelog' pk=virtualmachine.pk %}">Changelog</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
{% extends 'virtualization/virtualmachine.html' %}
|
||||||
|
|
||||||
|
{% block title %}{{ virtualmachine }} - Config Context{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<strong>Config Context</strong>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<pre>{{ virtualmachine.get_config_context }}</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
@ -1,5 +1,9 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
from rest_framework.decorators import detail_route
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from dcim.models import Interface
|
from dcim.models import Interface
|
||||||
from extras.api.views import CustomFieldModelViewSet
|
from extras.api.views import CustomFieldModelViewSet
|
||||||
from utilities.api import FieldChoicesViewSet, ModelViewSet
|
from utilities.api import FieldChoicesViewSet, ModelViewSet
|
||||||
@ -49,6 +53,11 @@ class VirtualMachineViewSet(CustomFieldModelViewSet):
|
|||||||
serializer_class = serializers.VirtualMachineSerializer
|
serializer_class = serializers.VirtualMachineSerializer
|
||||||
filter_class = filters.VirtualMachineFilter
|
filter_class = filters.VirtualMachineFilter
|
||||||
|
|
||||||
|
@detail_route(url_path='config-context')
|
||||||
|
def config_context(self, request, pk):
|
||||||
|
device = get_object_or_404(VirtualMachine, pk=pk)
|
||||||
|
return Response(device.get_config_context())
|
||||||
|
|
||||||
|
|
||||||
class InterfaceViewSet(ModelViewSet):
|
class InterfaceViewSet(ModelViewSet):
|
||||||
queryset = Interface.objects.filter(virtual_machine__isnull=False).select_related('virtual_machine')
|
queryset = Interface.objects.filter(virtual_machine__isnull=False).select_related('virtual_machine')
|
||||||
|
@ -9,7 +9,7 @@ from django.utils.encoding import python_2_unicode_compatible
|
|||||||
from taggit.managers import TaggableManager
|
from taggit.managers import TaggableManager
|
||||||
|
|
||||||
from dcim.models import Device
|
from dcim.models import Device
|
||||||
from extras.models import CustomFieldModel
|
from extras.models import ConfigContextModel, CustomFieldModel
|
||||||
from utilities.models import ChangeLoggedModel
|
from utilities.models import ChangeLoggedModel
|
||||||
from .constants import DEVICE_STATUS_ACTIVE, VM_STATUS_CHOICES, VM_STATUS_CLASSES
|
from .constants import DEVICE_STATUS_ACTIVE, VM_STATUS_CHOICES, VM_STATUS_CLASSES
|
||||||
|
|
||||||
@ -168,7 +168,7 @@ class Cluster(ChangeLoggedModel, CustomFieldModel):
|
|||||||
#
|
#
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class VirtualMachine(ChangeLoggedModel, CustomFieldModel):
|
class VirtualMachine(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
|
||||||
"""
|
"""
|
||||||
A virtual machine which runs inside a Cluster.
|
A virtual machine which runs inside a Cluster.
|
||||||
"""
|
"""
|
||||||
|
@ -48,6 +48,7 @@ urlpatterns = [
|
|||||||
url(r'^virtual-machines/(?P<pk>\d+)/$', views.VirtualMachineView.as_view(), name='virtualmachine'),
|
url(r'^virtual-machines/(?P<pk>\d+)/$', views.VirtualMachineView.as_view(), name='virtualmachine'),
|
||||||
url(r'^virtual-machines/(?P<pk>\d+)/edit/$', views.VirtualMachineEditView.as_view(), name='virtualmachine_edit'),
|
url(r'^virtual-machines/(?P<pk>\d+)/edit/$', views.VirtualMachineEditView.as_view(), name='virtualmachine_edit'),
|
||||||
url(r'^virtual-machines/(?P<pk>\d+)/delete/$', views.VirtualMachineDeleteView.as_view(), name='virtualmachine_delete'),
|
url(r'^virtual-machines/(?P<pk>\d+)/delete/$', views.VirtualMachineDeleteView.as_view(), name='virtualmachine_delete'),
|
||||||
|
url(r'^virtual-machines/(?P<pk>\d+)/config-context/$', views.VirtualMachineConfigContextView.as_view(), name='virtualmachine_configcontext'),
|
||||||
url(r'^virtual-machines/(?P<pk>\d+)/changelog/$', ObjectChangeLogView.as_view(), name='virtualmachine_changelog', kwargs={'model': VirtualMachine}),
|
url(r'^virtual-machines/(?P<pk>\d+)/changelog/$', ObjectChangeLogView.as_view(), name='virtualmachine_changelog', kwargs={'model': VirtualMachine}),
|
||||||
url(r'^virtual-machines/(?P<virtualmachine>\d+)/services/assign/$', ServiceCreateView.as_view(), name='virtualmachine_service_assign'),
|
url(r'^virtual-machines/(?P<virtualmachine>\d+)/services/assign/$', ServiceCreateView.as_view(), name='virtualmachine_service_assign'),
|
||||||
|
|
||||||
|
@ -269,6 +269,18 @@ class VirtualMachineView(View):
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class VirtualMachineConfigContextView(View):
|
||||||
|
|
||||||
|
def get(self, request, pk):
|
||||||
|
|
||||||
|
virtualmachine = get_object_or_404(VirtualMachine, pk=pk)
|
||||||
|
|
||||||
|
return render(request, 'virtualization/virtualmachine_configcontext.html', {
|
||||||
|
'virtualmachine': virtualmachine,
|
||||||
|
'active_tab': 'config-context',
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
class VirtualMachineCreateView(PermissionRequiredMixin, ObjectEditView):
|
class VirtualMachineCreateView(PermissionRequiredMixin, ObjectEditView):
|
||||||
permission_required = 'virtualization.add_virtualmachine'
|
permission_required = 'virtualization.add_virtualmachine'
|
||||||
model = VirtualMachine
|
model = VirtualMachine
|
||||||
|
Loading…
Reference in New Issue
Block a user