mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Merge branch 'develop' into 2921-tags-select2
This commit is contained in:
@ -29,7 +29,7 @@ server {
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8001;
|
||||
proxy_set_header X-Forwarded-Host $server_name;
|
||||
proxy_set_header X-Forwarded-Host $http_host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
@ -110,8 +110,8 @@ AUTH_LDAP_USER_FLAGS_BY_GROUP = {
|
||||
AUTH_LDAP_FIND_GROUP_PERMS = True
|
||||
|
||||
# Cache groups for one hour to reduce LDAP traffic
|
||||
AUTH_LDAP_CACHE_GROUPS = True
|
||||
AUTH_LDAP_GROUP_CACHE_TIMEOUT = 3600
|
||||
AUTH_LDAP_CACHE_TIMEOUT = 3600
|
||||
|
||||
```
|
||||
|
||||
* `is_active` - All users must be mapped to at least this group to enable authentication. Without this, users cannot log in.
|
||||
|
@ -1,10 +1,22 @@
|
||||
# v2.7.3 (FUTURE)
|
||||
# v2.7.4 (FUTURE)
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
* [#4043](https://github.com/netbox-community/netbox/issues/4043) - Fix toggling of required fields in custom scripts
|
||||
* [#4049](https://github.com/netbox-community/netbox/issues/4049) - Restore missing `tags` field in IPAM service serializer
|
||||
* [#4056](https://github.com/netbox-community/netbox/issues/4056) - Repair schema migration for Rack.outer_unit (from #3569)
|
||||
|
||||
---
|
||||
|
||||
# v2.7.3 (2020-01-28)
|
||||
|
||||
## Enhancements
|
||||
|
||||
* [#2921](https://github.com/netbox-community/netbox/issues/2921) - Replace tags filter with Select2 widget
|
||||
* [#3310](https://github.com/netbox-community/netbox/issues/3310) - Pre-select site/rack for B side when creating a new cable
|
||||
* [#3338](https://github.com/netbox-community/netbox/issues/3338) - Include circuit terminations in API representation of circuits
|
||||
* [#3509](https://github.com/netbox-community/netbox/issues/3509) - Add IP address variables for custom scripts
|
||||
* [#3978](https://github.com/netbox-community/netbox/issues/3978) - Add VRF filtering to search NAT IP
|
||||
* [#4005](https://github.com/netbox-community/netbox/issues/4005) - Include timezone context in webhook timestamps
|
||||
|
||||
## Bug Fixes
|
||||
@ -15,6 +27,14 @@
|
||||
* [#3989](https://github.com/netbox-community/netbox/issues/3989) - Correct HTTP content type assignment for webhooks
|
||||
* [#3999](https://github.com/netbox-community/netbox/issues/3999) - Do not filter child results by null if non-required parent fields are blank
|
||||
* [#4008](https://github.com/netbox-community/netbox/issues/4008) - Toggle rack elevation face using front/rear strings
|
||||
* [#4017](https://github.com/netbox-community/netbox/issues/4017) - Remove redundant tenant field from cluster form
|
||||
* [#4019](https://github.com/netbox-community/netbox/issues/4019) - Restore border around background devices in rack elevations
|
||||
* [#4022](https://github.com/netbox-community/netbox/issues/4022) - Fix display of assigned IPs when filtering device interfaces
|
||||
* [#4025](https://github.com/netbox-community/netbox/issues/4025) - Correct display of cable status (various places)
|
||||
* [#4027](https://github.com/netbox-community/netbox/issues/4027) - Repair schema migration for #3569 to convert IP addresses with DHCP status
|
||||
* [#4028](https://github.com/netbox-community/netbox/issues/4028) - Correct URL patterns to match Unicode characters in tag slugs
|
||||
* [#4030](https://github.com/netbox-community/netbox/issues/4030) - Fix exception when setting interfaces to tagged mode in bulk
|
||||
* [#4033](https://github.com/netbox-community/netbox/issues/4033) - Restore missing comments field label of various bulk edit forms
|
||||
|
||||
---
|
||||
|
||||
|
@ -3,11 +3,11 @@ from taggit_serializer.serializers import TaggitSerializer, TagListSerializerFie
|
||||
|
||||
from circuits.choices import CircuitStatusChoices
|
||||
from circuits.models import Provider, Circuit, CircuitTermination, CircuitType
|
||||
from dcim.api.nested_serializers import NestedCableSerializer, NestedSiteSerializer
|
||||
from dcim.api.nested_serializers import NestedCableSerializer, NestedInterfaceSerializer, NestedSiteSerializer
|
||||
from dcim.api.serializers import ConnectedEndpointSerializer
|
||||
from extras.api.customfields import CustomFieldModelSerializer
|
||||
from tenancy.api.nested_serializers import NestedTenantSerializer
|
||||
from utilities.api import ChoiceField, ValidatedModelSerializer
|
||||
from utilities.api import ChoiceField, ValidatedModelSerializer, WritableNestedSerializer
|
||||
from .nested_serializers import *
|
||||
|
||||
|
||||
@ -39,18 +39,30 @@ class CircuitTypeSerializer(ValidatedModelSerializer):
|
||||
fields = ['id', 'name', 'slug', 'description', 'circuit_count']
|
||||
|
||||
|
||||
class CircuitCircuitTerminationSerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuittermination-detail')
|
||||
site = NestedSiteSerializer()
|
||||
connected_endpoint = NestedInterfaceSerializer()
|
||||
|
||||
class Meta:
|
||||
model = CircuitTermination
|
||||
fields = ['id', 'url', 'site', 'connected_endpoint', 'port_speed', 'upstream_speed', 'xconnect_id']
|
||||
|
||||
|
||||
class CircuitSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
||||
provider = NestedProviderSerializer()
|
||||
status = ChoiceField(choices=CircuitStatusChoices, required=False)
|
||||
type = NestedCircuitTypeSerializer()
|
||||
tenant = NestedTenantSerializer(required=False, allow_null=True)
|
||||
termination_a = CircuitCircuitTerminationSerializer(read_only=True)
|
||||
termination_z = CircuitCircuitTerminationSerializer(read_only=True)
|
||||
tags = TagListSerializerField(required=False)
|
||||
|
||||
class Meta:
|
||||
model = Circuit
|
||||
fields = [
|
||||
'id', 'cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'commit_rate', 'description',
|
||||
'comments', 'tags', 'custom_fields', 'created', 'last_updated',
|
||||
'termination_a', 'termination_z', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
|
||||
]
|
||||
|
||||
|
||||
|
@ -62,7 +62,9 @@ class CircuitTypeViewSet(ModelViewSet):
|
||||
#
|
||||
|
||||
class CircuitViewSet(CustomFieldModelViewSet):
|
||||
queryset = Circuit.objects.prefetch_related('type', 'tenant', 'provider').prefetch_related('tags')
|
||||
queryset = Circuit.objects.prefetch_related(
|
||||
'type', 'tenant', 'provider', 'terminations__site', 'terminations__connected_endpoint__device'
|
||||
).prefetch_related('tags')
|
||||
serializer_class = serializers.CircuitSerializer
|
||||
filterset_class = filters.CircuitFilterSet
|
||||
|
||||
|
@ -89,7 +89,8 @@ class ProviderBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEdi
|
||||
label='Admin contact'
|
||||
)
|
||||
comments = CommentField(
|
||||
widget=SmallTextarea()
|
||||
widget=SmallTextarea,
|
||||
label='Comments'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -678,7 +678,8 @@ class RackBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
|
||||
widget=StaticSelect2()
|
||||
)
|
||||
comments = CommentField(
|
||||
widget=SmallTextarea
|
||||
widget=SmallTextarea,
|
||||
label='Comments'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
@ -2758,7 +2759,7 @@ class InterfaceCSVForm(forms.ModelForm):
|
||||
return self.cleaned_data['enabled']
|
||||
|
||||
|
||||
class InterfaceBulkEditForm(InterfaceCommonForm, BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
|
||||
class InterfaceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=Interface.objects.all(),
|
||||
widget=forms.MultipleHiddenInput()
|
||||
@ -2839,6 +2840,18 @@ class InterfaceBulkEditForm(InterfaceCommonForm, BootstrapMixin, AddRemoveTagsFo
|
||||
else:
|
||||
self.fields['lag'].choices = []
|
||||
|
||||
def clean(self):
|
||||
|
||||
# Untagged interfaces cannot be assigned tagged VLANs
|
||||
if self.cleaned_data['mode'] == InterfaceModeChoices.MODE_ACCESS and self.cleaned_data['tagged_vlans']:
|
||||
raise forms.ValidationError({
|
||||
'mode': "An access interface cannot have tagged VLANs assigned."
|
||||
})
|
||||
|
||||
# Remove all tagged VLAN assignments from "tagged all" interfaces
|
||||
elif self.cleaned_data['mode'] == InterfaceModeChoices.MODE_TAGGED_ALL:
|
||||
self.cleaned_data['tagged_vlans'] = []
|
||||
|
||||
|
||||
class InterfaceBulkRenameForm(BulkRenameForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
@ -4421,8 +4434,9 @@ class PowerFeedBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd
|
||||
max_utilization = forms.IntegerField(
|
||||
required=False
|
||||
)
|
||||
comments = forms.CharField(
|
||||
required=False
|
||||
comments = CommentField(
|
||||
widget=SmallTextarea,
|
||||
label='Comments'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -37,7 +37,7 @@ def rack_status_to_slug(apps, schema_editor):
|
||||
def rack_outer_unit_to_slug(apps, schema_editor):
|
||||
Rack = apps.get_model('dcim', 'Rack')
|
||||
for id, slug in RACK_DIMENSION_CHOICES:
|
||||
Rack.objects.filter(status=str(id)).update(status=slug)
|
||||
Rack.objects.filter(outer_unit=str(id)).update(outer_unit=slug)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
27
netbox/dcim/migrations/0092_fix_rack_outer_unit.py
Normal file
27
netbox/dcim/migrations/0092_fix_rack_outer_unit.py
Normal file
@ -0,0 +1,27 @@
|
||||
from django.db import migrations
|
||||
|
||||
RACK_DIMENSION_CHOICES = (
|
||||
(1000, 'mm'),
|
||||
(2000, 'in'),
|
||||
)
|
||||
|
||||
|
||||
def rack_outer_unit_to_slug(apps, schema_editor):
|
||||
Rack = apps.get_model('dcim', 'Rack')
|
||||
for id, slug in RACK_DIMENSION_CHOICES:
|
||||
Rack.objects.filter(outer_unit=str(id)).update(outer_unit=slug)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('dcim', '0091_interface_type_other'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
# Fixes a missed field migration from #3569; see bug #4056. The original migration has also been fixed,
|
||||
# so this can be omitted when squashing in the future.
|
||||
migrations.RunPython(
|
||||
code=rack_outer_unit_to_slug
|
||||
),
|
||||
]
|
@ -405,7 +405,7 @@ class RackElevationHelperMixin:
|
||||
|
||||
@staticmethod
|
||||
def _draw_device_rear(drawing, device, start, end, text):
|
||||
rect = drawing.rect(start, end, class_="blocked")
|
||||
rect = drawing.rect(start, end, class_="slot blocked")
|
||||
rect.set_desc('{} — {} ({}U) {} {}'.format(
|
||||
device.device_role, device.device_type.display_name,
|
||||
device.device_type.u_height, device.asset_tag or '', device.serial or ''
|
||||
|
@ -2,6 +2,7 @@ from django.test import TestCase
|
||||
|
||||
from dcim.forms import *
|
||||
from dcim.models import *
|
||||
from virtualization.models import Cluster, ClusterGroup, ClusterType
|
||||
|
||||
|
||||
def get_id(model, slug):
|
||||
@ -10,83 +11,108 @@ def get_id(model, slug):
|
||||
|
||||
class DeviceTestCase(TestCase):
|
||||
|
||||
fixtures = ['dcim', 'ipam', 'virtualization']
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
|
||||
site = Site.objects.create(name='Site 1', slug='site-1')
|
||||
rack = Rack.objects.create(name='Rack 1', site=site)
|
||||
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
|
||||
device_type = DeviceType.objects.create(
|
||||
manufacturer=manufacturer, model='Device Type 1', slug='device-type-1', u_height=1
|
||||
)
|
||||
device_role = DeviceRole.objects.create(
|
||||
name='Device Role 1', slug='device-role-1', color='ff0000'
|
||||
)
|
||||
Platform.objects.create(name='Platform 1', slug='platform-1')
|
||||
Device.objects.create(
|
||||
name='Device 1', device_type=device_type, device_role=device_role, site=site, rack=rack, position=1
|
||||
)
|
||||
cluster_type = ClusterType.objects.create(name='Cluster Type 1', slug='cluster-type-1')
|
||||
cluster_group = ClusterGroup.objects.create(name='Cluster Group 1', slug='cluster-group-1')
|
||||
Cluster.objects.create(name='Cluster 1', type=cluster_type, group=cluster_group)
|
||||
|
||||
def test_racked_device(self):
|
||||
test = DeviceForm(data={
|
||||
'name': 'test',
|
||||
'device_role': get_id(DeviceRole, 'leaf-switch'),
|
||||
form = DeviceForm(data={
|
||||
'name': 'New Device',
|
||||
'device_role': DeviceRole.objects.first().pk,
|
||||
'tenant': None,
|
||||
'manufacturer': get_id(Manufacturer, 'juniper'),
|
||||
'device_type': get_id(DeviceType, 'qfx5100-48s'),
|
||||
'site': get_id(Site, 'test1'),
|
||||
'rack': '1',
|
||||
'manufacturer': Manufacturer.objects.first().pk,
|
||||
'device_type': DeviceType.objects.first().pk,
|
||||
'site': Site.objects.first().pk,
|
||||
'rack': Rack.objects.first().pk,
|
||||
'face': DeviceFaceChoices.FACE_FRONT,
|
||||
'position': 41,
|
||||
'platform': get_id(Platform, 'juniper-junos'),
|
||||
'position': 2,
|
||||
'platform': Platform.objects.first().pk,
|
||||
'status': DeviceStatusChoices.STATUS_ACTIVE,
|
||||
})
|
||||
self.assertTrue(test.is_valid(), test.fields['position'].choices)
|
||||
self.assertTrue(test.save())
|
||||
self.assertTrue(form.is_valid())
|
||||
self.assertTrue(form.save())
|
||||
|
||||
def test_racked_device_occupied(self):
|
||||
test = DeviceForm(data={
|
||||
form = DeviceForm(data={
|
||||
'name': 'test',
|
||||
'device_role': get_id(DeviceRole, 'leaf-switch'),
|
||||
'device_role': DeviceRole.objects.first().pk,
|
||||
'tenant': None,
|
||||
'manufacturer': get_id(Manufacturer, 'juniper'),
|
||||
'device_type': get_id(DeviceType, 'qfx5100-48s'),
|
||||
'site': get_id(Site, 'test1'),
|
||||
'rack': '1',
|
||||
'manufacturer': Manufacturer.objects.first().pk,
|
||||
'device_type': DeviceType.objects.first().pk,
|
||||
'site': Site.objects.first().pk,
|
||||
'rack': Rack.objects.first().pk,
|
||||
'face': DeviceFaceChoices.FACE_FRONT,
|
||||
'position': 1,
|
||||
'platform': get_id(Platform, 'juniper-junos'),
|
||||
'platform': Platform.objects.first().pk,
|
||||
'status': DeviceStatusChoices.STATUS_ACTIVE,
|
||||
})
|
||||
self.assertFalse(test.is_valid())
|
||||
self.assertFalse(form.is_valid())
|
||||
self.assertIn('position', form.errors)
|
||||
|
||||
def test_non_racked_device(self):
|
||||
test = DeviceForm(data={
|
||||
'name': 'test',
|
||||
'device_role': get_id(DeviceRole, 'pdu'),
|
||||
form = DeviceForm(data={
|
||||
'name': 'New Device',
|
||||
'device_role': DeviceRole.objects.first().pk,
|
||||
'tenant': None,
|
||||
'manufacturer': get_id(Manufacturer, 'servertech'),
|
||||
'device_type': get_id(DeviceType, 'cwg-24vym415c9'),
|
||||
'site': get_id(Site, 'test1'),
|
||||
'rack': '1',
|
||||
'face': '',
|
||||
'manufacturer': Manufacturer.objects.first().pk,
|
||||
'device_type': DeviceType.objects.first().pk,
|
||||
'site': Site.objects.first().pk,
|
||||
'rack': None,
|
||||
'face': None,
|
||||
'position': None,
|
||||
'platform': None,
|
||||
'platform': Platform.objects.first().pk,
|
||||
'status': DeviceStatusChoices.STATUS_ACTIVE,
|
||||
})
|
||||
self.assertTrue(test.is_valid())
|
||||
self.assertTrue(test.save())
|
||||
self.assertTrue(form.is_valid())
|
||||
self.assertTrue(form.save())
|
||||
|
||||
def test_non_racked_device_with_face(self):
|
||||
test = DeviceForm(data={
|
||||
'name': 'test',
|
||||
'device_role': get_id(DeviceRole, 'pdu'),
|
||||
def test_non_racked_device_with_face_position(self):
|
||||
form = DeviceForm(data={
|
||||
'name': 'New Device',
|
||||
'device_role': DeviceRole.objects.first().pk,
|
||||
'tenant': None,
|
||||
'manufacturer': get_id(Manufacturer, 'servertech'),
|
||||
'device_type': get_id(DeviceType, 'cwg-24vym415c9'),
|
||||
'site': get_id(Site, 'test1'),
|
||||
'rack': '1',
|
||||
'manufacturer': Manufacturer.objects.first().pk,
|
||||
'device_type': DeviceType.objects.first().pk,
|
||||
'site': Site.objects.first().pk,
|
||||
'rack': None,
|
||||
'face': DeviceFaceChoices.FACE_REAR,
|
||||
'position': None,
|
||||
'position': 10,
|
||||
'platform': None,
|
||||
'status': DeviceStatusChoices.STATUS_ACTIVE,
|
||||
})
|
||||
self.assertTrue(test.is_valid())
|
||||
self.assertTrue(test.save())
|
||||
self.assertFalse(form.is_valid())
|
||||
self.assertIn('face', form.errors)
|
||||
self.assertIn('position', form.errors)
|
||||
|
||||
def test_cloned_cluster_device_initial_data(self):
|
||||
def test_initial_data_population(self):
|
||||
device_type = DeviceType.objects.first()
|
||||
cluster = Cluster.objects.first()
|
||||
test = DeviceForm(initial={
|
||||
'device_type': get_id(DeviceType, 'poweredge-r640'),
|
||||
'device_role': get_id(DeviceRole, 'server'),
|
||||
'device_type': device_type.pk,
|
||||
'device_role': DeviceRole.objects.first().pk,
|
||||
'status': DeviceStatusChoices.STATUS_ACTIVE,
|
||||
'site': get_id(Site, 'test1'),
|
||||
"cluster": Cluster.objects.get(id=4).id,
|
||||
'site': Site.objects.first().pk,
|
||||
'cluster': cluster.pk,
|
||||
})
|
||||
self.assertEqual(test.initial['manufacturer'], get_id(Manufacturer, 'dell'))
|
||||
self.assertIn('cluster_group', test.initial)
|
||||
self.assertEqual(test.initial['cluster_group'], get_id(ClusterGroup, 'vm-host'))
|
||||
|
||||
# Check that the initial value for the manufacturer is set automatically when assigning the device type
|
||||
self.assertEqual(test.initial['manufacturer'], device_type.manufacturer.pk)
|
||||
|
||||
# Check that the initial value for the cluster group is set automatically when assigning the cluster
|
||||
self.assertEqual(test.initial['cluster_group'], cluster.group.pk)
|
||||
|
@ -1,35 +0,0 @@
|
||||
[
|
||||
{
|
||||
"model": "extras.graph",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"type": 300,
|
||||
"weight": 1000,
|
||||
"name": "Site Test Graph",
|
||||
"source": "http://localhost/na.png",
|
||||
"link": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "extras.graph",
|
||||
"pk": 2,
|
||||
"fields": {
|
||||
"type": 200,
|
||||
"weight": 1000,
|
||||
"name": "Provider Test Graph",
|
||||
"source": "http://localhost/provider_graph.png",
|
||||
"link": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "extras.graph",
|
||||
"pk": 3,
|
||||
"fields": {
|
||||
"type": 100,
|
||||
"weight": 1000,
|
||||
"name": "Interface Test Graph",
|
||||
"source": "http://localhost/interface_graph.png",
|
||||
"link": ""
|
||||
}
|
||||
}
|
||||
]
|
@ -53,14 +53,15 @@ class ScriptVariable:
|
||||
# Initialize field attributes
|
||||
if not hasattr(self, 'field_attrs'):
|
||||
self.field_attrs = {}
|
||||
if description:
|
||||
self.field_attrs['help_text'] = description
|
||||
if label:
|
||||
self.field_attrs['label'] = label
|
||||
if description:
|
||||
self.field_attrs['help_text'] = description
|
||||
if default:
|
||||
self.field_attrs['initial'] = default
|
||||
if required:
|
||||
self.field_attrs['required'] = True
|
||||
self.field_attrs['required'] = required
|
||||
|
||||
# Initialize the list of optional validators if none have already been defined
|
||||
if 'validators' not in self.field_attrs:
|
||||
self.field_attrs['validators'] = []
|
||||
|
||||
|
@ -11,10 +11,10 @@ urlpatterns = [
|
||||
path(r'tags/', views.TagListView.as_view(), name='tag_list'),
|
||||
path(r'tags/edit/', views.TagBulkEditView.as_view(), name='tag_bulk_edit'),
|
||||
path(r'tags/delete/', views.TagBulkDeleteView.as_view(), name='tag_bulk_delete'),
|
||||
path(r'tags/<slug:slug>/', views.TagView.as_view(), name='tag'),
|
||||
path(r'tags/<slug:slug>/edit/', views.TagEditView.as_view(), name='tag_edit'),
|
||||
path(r'tags/<slug:slug>/delete/', views.TagDeleteView.as_view(), name='tag_delete'),
|
||||
path(r'tags/<slug:slug>/changelog/', views.ObjectChangeLogView.as_view(), name='tag_changelog', kwargs={'model': Tag}),
|
||||
path(r'tags/<str:slug>/', views.TagView.as_view(), name='tag'),
|
||||
path(r'tags/<str:slug>/edit/', views.TagEditView.as_view(), name='tag_edit'),
|
||||
path(r'tags/<str:slug>/delete/', views.TagDeleteView.as_view(), name='tag_delete'),
|
||||
path(r'tags/<str:slug>/changelog/', views.ObjectChangeLogView.as_view(), name='tag_changelog', kwargs={'model': Tag}),
|
||||
|
||||
# Config contexts
|
||||
path(r'config-contexts/', views.ConfigContextListView.as_view(), name='configcontext_list'),
|
||||
|
@ -237,7 +237,7 @@ class AvailableIPSerializer(serializers.Serializer):
|
||||
# Services
|
||||
#
|
||||
|
||||
class ServiceSerializer(CustomFieldModelSerializer):
|
||||
class ServiceSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
||||
device = NestedDeviceSerializer(required=False, allow_null=True)
|
||||
virtual_machine = NestedVirtualMachineSerializer(required=False, allow_null=True)
|
||||
protocol = ChoiceField(choices=ServiceProtocolChoices)
|
||||
@ -247,10 +247,11 @@ class ServiceSerializer(CustomFieldModelSerializer):
|
||||
required=False,
|
||||
many=True
|
||||
)
|
||||
tags = TagListSerializerField(required=False)
|
||||
|
||||
class Meta:
|
||||
model = Service
|
||||
fields = [
|
||||
'id', 'device', 'virtual_machine', 'name', 'port', 'protocol', 'ipaddresses', 'description',
|
||||
'id', 'device', 'virtual_machine', 'name', 'port', 'protocol', 'ipaddresses', 'description', 'tags',
|
||||
'custom_fields', 'created', 'last_updated',
|
||||
]
|
||||
|
@ -1,329 +0,0 @@
|
||||
[
|
||||
{
|
||||
"model": "ipam.rir",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"name": "RFC1918",
|
||||
"slug": "rfc1918"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "ipam.aggregate",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"created": "2016-06-23",
|
||||
"last_updated": "2016-06-23T03:19:56.521Z",
|
||||
"family": 4,
|
||||
"prefix": "10.0.0.0/8",
|
||||
"rir": 1,
|
||||
"date_added": null,
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "ipam.role",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"name": "Lab Network",
|
||||
"slug": "lab-network",
|
||||
"weight": 1000
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "ipam.prefix",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"created": "2016-06-23",
|
||||
"last_updated": "2016-06-23T03:19:56.521Z",
|
||||
"family": 4,
|
||||
"prefix": "10.1.1.0/24",
|
||||
"site": 1,
|
||||
"vrf": null,
|
||||
"vlan": null,
|
||||
"status": "active",
|
||||
"role": 1,
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "ipam.prefix",
|
||||
"pk": 2,
|
||||
"fields": {
|
||||
"created": "2016-06-23",
|
||||
"last_updated": "2016-06-23T03:19:56.521Z",
|
||||
"family": 4,
|
||||
"prefix": "10.0.255.0/24",
|
||||
"site": 1,
|
||||
"vrf": null,
|
||||
"vlan": null,
|
||||
"status": "active",
|
||||
"role": 1,
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "ipam.ipaddress",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"created": "2016-06-23",
|
||||
"last_updated": "2016-06-23T03:19:56.521Z",
|
||||
"family": 4,
|
||||
"address": "10.0.255.1/32",
|
||||
"vrf": null,
|
||||
"interface_id": 3,
|
||||
"nat_inside": null,
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "ipam.ipaddress",
|
||||
"pk": 2,
|
||||
"fields": {
|
||||
"created": "2016-06-23",
|
||||
"last_updated": "2016-06-23T03:19:56.521Z",
|
||||
"family": 4,
|
||||
"address": "169.254.254.1/31",
|
||||
"vrf": null,
|
||||
"interface_id": 4,
|
||||
"nat_inside": null,
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "ipam.ipaddress",
|
||||
"pk": 3,
|
||||
"fields": {
|
||||
"created": "2016-06-23",
|
||||
"last_updated": "2016-06-23T03:19:56.521Z",
|
||||
"family": 4,
|
||||
"address": "10.0.255.2/32",
|
||||
"vrf": null,
|
||||
"interface_id": 185,
|
||||
"nat_inside": null,
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "ipam.ipaddress",
|
||||
"pk": 4,
|
||||
"fields": {
|
||||
"created": "2016-06-23",
|
||||
"last_updated": "2016-06-23T03:19:56.521Z",
|
||||
"family": 4,
|
||||
"address": "169.254.1.1/31",
|
||||
"vrf": null,
|
||||
"interface_id": 213,
|
||||
"nat_inside": null,
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "ipam.ipaddress",
|
||||
"pk": 5,
|
||||
"fields": {
|
||||
"created": "2016-06-23",
|
||||
"last_updated": "2016-06-23T03:19:56.521Z",
|
||||
"family": 4,
|
||||
"address": "10.0.254.1/24",
|
||||
"vrf": null,
|
||||
"interface_id": 12,
|
||||
"nat_inside": null,
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "ipam.ipaddress",
|
||||
"pk": 8,
|
||||
"fields": {
|
||||
"created": "2016-06-23",
|
||||
"last_updated": "2016-06-23T03:19:56.521Z",
|
||||
"family": 4,
|
||||
"address": "10.15.21.1/31",
|
||||
"vrf": null,
|
||||
"interface_id": 218,
|
||||
"nat_inside": null,
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "ipam.ipaddress",
|
||||
"pk": 9,
|
||||
"fields": {
|
||||
"created": "2016-06-23",
|
||||
"last_updated": "2016-06-23T03:19:56.521Z",
|
||||
"family": 4,
|
||||
"address": "10.15.21.2/31",
|
||||
"vrf": null,
|
||||
"interface_id": 9,
|
||||
"nat_inside": null,
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "ipam.ipaddress",
|
||||
"pk": 10,
|
||||
"fields": {
|
||||
"created": "2016-06-23",
|
||||
"last_updated": "2016-06-23T03:19:56.521Z",
|
||||
"family": 4,
|
||||
"address": "10.15.22.1/31",
|
||||
"vrf": null,
|
||||
"interface_id": 8,
|
||||
"nat_inside": null,
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "ipam.ipaddress",
|
||||
"pk": 11,
|
||||
"fields": {
|
||||
"created": "2016-06-23",
|
||||
"last_updated": "2016-06-23T03:19:56.521Z",
|
||||
"family": 4,
|
||||
"address": "10.15.20.1/31",
|
||||
"vrf": null,
|
||||
"interface_id": 7,
|
||||
"nat_inside": null,
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "ipam.ipaddress",
|
||||
"pk": 12,
|
||||
"fields": {
|
||||
"created": "2016-06-23",
|
||||
"last_updated": "2016-06-23T03:19:56.521Z",
|
||||
"family": 4,
|
||||
"address": "10.16.20.1/31",
|
||||
"vrf": null,
|
||||
"interface_id": 216,
|
||||
"nat_inside": null,
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "ipam.ipaddress",
|
||||
"pk": 13,
|
||||
"fields": {
|
||||
"created": "2016-06-23",
|
||||
"last_updated": "2016-06-23T03:19:56.521Z",
|
||||
"family": 4,
|
||||
"address": "10.15.22.2/31",
|
||||
"vrf": null,
|
||||
"interface_id": 206,
|
||||
"nat_inside": null,
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "ipam.ipaddress",
|
||||
"pk": 14,
|
||||
"fields": {
|
||||
"created": "2016-06-23",
|
||||
"last_updated": "2016-06-23T03:19:56.521Z",
|
||||
"family": 4,
|
||||
"address": "10.16.22.1/31",
|
||||
"vrf": null,
|
||||
"interface_id": 217,
|
||||
"nat_inside": null,
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "ipam.ipaddress",
|
||||
"pk": 15,
|
||||
"fields": {
|
||||
"created": "2016-06-23",
|
||||
"last_updated": "2016-06-23T03:19:56.521Z",
|
||||
"family": 4,
|
||||
"address": "10.16.22.2/31",
|
||||
"vrf": null,
|
||||
"interface_id": 205,
|
||||
"nat_inside": null,
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "ipam.ipaddress",
|
||||
"pk": 16,
|
||||
"fields": {
|
||||
"created": "2016-06-23",
|
||||
"last_updated": "2016-06-23T03:19:56.521Z",
|
||||
"family": 4,
|
||||
"address": "10.16.20.2/31",
|
||||
"vrf": null,
|
||||
"interface_id": 211,
|
||||
"nat_inside": null,
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "ipam.ipaddress",
|
||||
"pk": 17,
|
||||
"fields": {
|
||||
"created": "2016-06-23",
|
||||
"last_updated": "2016-06-23T03:19:56.521Z",
|
||||
"family": 4,
|
||||
"address": "10.15.22.2/31",
|
||||
"vrf": null,
|
||||
"interface_id": 212,
|
||||
"nat_inside": null,
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "ipam.ipaddress",
|
||||
"pk": 19,
|
||||
"fields": {
|
||||
"created": "2016-06-23",
|
||||
"last_updated": "2016-06-23T03:19:56.521Z",
|
||||
"family": 4,
|
||||
"address": "10.0.254.2/32",
|
||||
"vrf": null,
|
||||
"interface_id": 188,
|
||||
"nat_inside": null,
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "ipam.ipaddress",
|
||||
"pk": 20,
|
||||
"fields": {
|
||||
"created": "2016-06-23",
|
||||
"last_updated": "2016-06-23T03:19:56.521Z",
|
||||
"family": 4,
|
||||
"address": "169.254.1.1/31",
|
||||
"vrf": null,
|
||||
"interface_id": 200,
|
||||
"nat_inside": null,
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "ipam.ipaddress",
|
||||
"pk": 21,
|
||||
"fields": {
|
||||
"created": "2016-06-23",
|
||||
"last_updated": "2016-06-23T03:19:56.521Z",
|
||||
"family": 4,
|
||||
"address": "169.254.1.2/31",
|
||||
"vrf": null,
|
||||
"interface_id": 194,
|
||||
"nat_inside": null,
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "ipam.vlan",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"created": "2016-06-23",
|
||||
"last_updated": "2016-06-23T03:19:56.521Z",
|
||||
"site": 1,
|
||||
"vid": 999,
|
||||
"name": "TEST",
|
||||
"status": "active",
|
||||
"role": 1
|
||||
}
|
||||
}
|
||||
]
|
@ -638,6 +638,17 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
|
||||
}
|
||||
)
|
||||
)
|
||||
nat_vrf = forms.ModelChoiceField(
|
||||
queryset=VRF.objects.all(),
|
||||
required=False,
|
||||
label='VRF',
|
||||
widget=APISelect(
|
||||
api_url="/api/ipam/vrfs/",
|
||||
filter_for={
|
||||
'nat_inside': 'vrf_id'
|
||||
}
|
||||
)
|
||||
)
|
||||
nat_inside = ChainedModelChoiceField(
|
||||
queryset=IPAddress.objects.all(),
|
||||
chains=(
|
||||
|
@ -2,10 +2,10 @@ from django.db import migrations, models
|
||||
|
||||
|
||||
IPADDRESS_STATUS_CHOICES = (
|
||||
(0, 'container'),
|
||||
(1, 'active'),
|
||||
(2, 'reserved'),
|
||||
(3, 'deprecated'),
|
||||
(5, 'dhcp'),
|
||||
)
|
||||
|
||||
IPADDRESS_ROLE_CHOICES = (
|
||||
|
21
netbox/ipam/migrations/0034_fix_ipaddress_status_dhcp.py
Normal file
21
netbox/ipam/migrations/0034_fix_ipaddress_status_dhcp.py
Normal file
@ -0,0 +1,21 @@
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def ipaddress_status_dhcp_to_slug(apps, schema_editor):
|
||||
IPAddress = apps.get_model('ipam', 'IPAddress')
|
||||
IPAddress.objects.filter(status='5').update(status='dhcp')
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('ipam', '0033_deterministic_ordering'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
# Fixes a missed integer substitution from #3569; see bug #4027. The original migration has also been fixed,
|
||||
# so this can be omitted when squashing in the future.
|
||||
migrations.RunPython(
|
||||
code=ipaddress_status_dhcp_to_slug
|
||||
),
|
||||
]
|
@ -1064,6 +1064,7 @@ class ServiceTest(APITestCase):
|
||||
'name': 'Test Service 4',
|
||||
'protocol': ServiceProtocolChoices.PROTOCOL_TCP,
|
||||
'port': 4,
|
||||
'tags': ['Foo', 'Bar'],
|
||||
}
|
||||
|
||||
url = reverse('ipam-api:service-list')
|
||||
@ -1076,6 +1077,8 @@ class ServiceTest(APITestCase):
|
||||
self.assertEqual(service4.name, data['name'])
|
||||
self.assertEqual(service4.protocol, data['protocol'])
|
||||
self.assertEqual(service4.port, data['port'])
|
||||
tags = [tag.name for tag in service4.tags.all()]
|
||||
self.assertEqual(sorted(tags), sorted(data['tags']))
|
||||
|
||||
def test_create_service_bulk(self):
|
||||
|
||||
|
@ -12,7 +12,7 @@ from django.core.exceptions import ImproperlyConfigured
|
||||
# Environment setup
|
||||
#
|
||||
|
||||
VERSION = '2.7.3-dev'
|
||||
VERSION = '2.7.4-dev'
|
||||
|
||||
# Hostname
|
||||
HOSTNAME = platform.node()
|
||||
@ -503,6 +503,7 @@ SWAGGER_SETTINGS = {
|
||||
'utilities.custom_inspectors.IdInFilterInspector',
|
||||
'drf_yasg.inspectors.CoreAPICompatInspector',
|
||||
],
|
||||
'DEFAULT_INFO': 'netbox.urls.openapi_info',
|
||||
'DEFAULT_MODEL_DEPTH': 1,
|
||||
'DEFAULT_PAGINATOR_INSPECTORS': [
|
||||
'utilities.custom_inspectors.NullablePaginatorInspector',
|
||||
|
@ -9,14 +9,16 @@ from netbox.views import APIRootView, HomeView, SearchView
|
||||
from users.views import LoginView, LogoutView
|
||||
from .admin import admin_site
|
||||
|
||||
openapi_info = openapi.Info(
|
||||
title="NetBox API",
|
||||
default_version='v2',
|
||||
description="API to access NetBox",
|
||||
terms_of_service="https://github.com/netbox-community/netbox",
|
||||
license=openapi.License(name="Apache v2 License"),
|
||||
)
|
||||
|
||||
schema_view = get_schema_view(
|
||||
openapi.Info(
|
||||
title="NetBox API",
|
||||
default_version='v2',
|
||||
description="API to access NetBox",
|
||||
terms_of_service="https://github.com/netbox-community/netbox",
|
||||
license=openapi.License(name="Apache v2 License"),
|
||||
),
|
||||
openapi_info,
|
||||
validators=['flex', 'ssv'],
|
||||
public=True,
|
||||
)
|
||||
|
@ -2,9 +2,9 @@
|
||||
$('button.toggle-ips').click(function() {
|
||||
var selected = $(this).attr('selected');
|
||||
if (selected) {
|
||||
$('#interfaces_table tr.ipaddresses').hide();
|
||||
$('#interfaces_table tr.interface:visible + tr.ipaddresses').hide();
|
||||
} else {
|
||||
$('#interfaces_table tr.ipaddresses').show();
|
||||
$('#interfaces_table tr.interface:visible + tr.ipaddresses').show();
|
||||
}
|
||||
$(this).attr('selected', !selected);
|
||||
$(this).children('span').toggleClass('glyphicon-check glyphicon-unchecked');
|
||||
@ -14,17 +14,22 @@ $('button.toggle-ips').click(function() {
|
||||
// Inteface filtering
|
||||
$('input.interface-filter').on('input', function() {
|
||||
var filter = new RegExp(this.value);
|
||||
var interface;
|
||||
|
||||
for (interface of $(this).closest('div.panel').find('tbody > tr')) {
|
||||
for (interface of $('#interfaces_table > tbody > tr.interface')) {
|
||||
// Slice off 'interface_' at the start of the ID
|
||||
if (filter && filter.test(interface.id.slice(10))) {
|
||||
if (filter.test(interface.id.slice(10))) {
|
||||
// Match the toggle in case the filter now matches the interface
|
||||
$(interface).find('input:checkbox[name=pk]').prop('checked', $('input.toggle').prop('checked'));
|
||||
$(interface).show();
|
||||
if ($('button.toggle-ips').attr('selected')) {
|
||||
$(interface).next('tr.ipaddresses').show();
|
||||
}
|
||||
} else {
|
||||
// Uncheck to prevent actions from including it when it doesn't match
|
||||
$(interface).find('input:checkbox[name=pk]').prop('checked', false);
|
||||
$(interface).hide();
|
||||
$(interface).next('tr.ipaddresses').hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -32,7 +32,7 @@
|
||||
{% if cable.label %}<code>{{ cable.label }}</code>{% else %}Cable #{{ cable.pk }}{% endif %}
|
||||
</a>
|
||||
</h4>
|
||||
<p><span class="label label-{% if cable.status %}success{% else %}info{% endif %}">{{ cable.get_status_display }}</span></p>
|
||||
<p><span class="label label-{{ cable.get_status_class }}">{{ cable.get_status_display }}</span></p>
|
||||
<p>{{ cable.get_type_display|default:"" }}</p>
|
||||
{% if cable.length %}{{ cable.length }} {{ cable.get_length_unit_display }}{% endif %}
|
||||
{% if cable.color %}
|
||||
|
@ -1,5 +1,5 @@
|
||||
{% if perms.dcim.change_cable %}
|
||||
{% if cable.status %}
|
||||
{% if cable.status == 'connected' %}
|
||||
<a href="#" class="btn btn-warning btn-xs cable-toggle connected" title="Mark planned" data="{{ cable.pk }}">
|
||||
<i class="glyphicon glyphicon-ban-circle" aria-hidden="true"></i>
|
||||
</a>
|
||||
|
@ -1,4 +1,4 @@
|
||||
<tr class="consoleport{% if cp.cable.status %} success{% elif cp.cable %} info{% endif %}">
|
||||
<tr class="consoleport{% if cp.cable %} {{ cp.cable.get_status_class }}{% endif %}">
|
||||
|
||||
{# Name #}
|
||||
<td>
|
||||
|
@ -1,6 +1,6 @@
|
||||
{% load helpers %}
|
||||
|
||||
<tr class="consoleserverport{% if csp.cable.status %} success{% elif csp.cable %} info{% endif %}">
|
||||
<tr class="consoleserverport{% if csp.cable %} {{ csp.cable.get_status_class }}{% endif %}">
|
||||
|
||||
{# Checkbox #}
|
||||
{% if perms.dcim.change_consoleserverport or perms.dcim.delete_consoleserverport %}
|
||||
|
@ -1,5 +1,5 @@
|
||||
{% load helpers %}
|
||||
<tr class="frontport{% if frontport.cable.status %} success{% elif frontport.cable %} info{% endif %}">
|
||||
<tr class="frontport{% if frontport.cable %} {{ frontport.cable.get_status_class }}{% endif %}">
|
||||
|
||||
{# Checkbox #}
|
||||
{% if perms.dcim.change_frontport or perms.dcim.delete_frontport %}
|
||||
|
@ -1,5 +1,5 @@
|
||||
{% load helpers %}
|
||||
<tr class="interface{% if not iface.enabled %} danger{% elif iface.cable.status %} success{% elif iface.cable %} info{% elif iface.is_virtual %} warning{% endif %}" id="interface_{{ iface.name }}">
|
||||
<tr class="interface{% if not iface.enabled %} danger{% elif iface.cable %} {{ iface.cable.get_status_class }}{% elif iface.is_virtual %} warning{% endif %}" id="interface_{{ iface.name }}">
|
||||
|
||||
{# Checkbox #}
|
||||
{% if perms.dcim.change_interface or perms.dcim.delete_interface %}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{% load helpers %}
|
||||
|
||||
<tr class="poweroutlet{% if po.cable.status %} success{% elif po.cable %} info{% endif %}">
|
||||
<tr class="poweroutlet{% if po.cable %} {{ po.cable.get_status_class }}{% endif %}">
|
||||
|
||||
{# Checkbox #}
|
||||
{% if perms.dcim.change_poweroutlet or perms.dcim.delete_poweroutlet %}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<tr class="powerport{% if pp.cable.status %} success{% elif pp.cable %} info{% endif %}">
|
||||
<tr class="powerport{% if pp.cable %} {{ pp.cable.get_status_class }}{% endif %}">
|
||||
|
||||
{# Name #}
|
||||
<td>
|
||||
|
@ -1,5 +1,5 @@
|
||||
{% load helpers %}
|
||||
<tr class="rearport{% if rearport.cable.status %} success{% elif rearport.cable %} info{% endif %}">
|
||||
<tr class="rearport{% if rearport.cable %} {{ rearport.cable.get_status_class }}{% endif %}">
|
||||
|
||||
{# Checkbox #}
|
||||
{% if perms.dcim.change_rearport or perms.dcim.delete_rearport %}
|
||||
|
@ -61,7 +61,7 @@
|
||||
{% render_field form.nat_device %}
|
||||
</div>
|
||||
<div class="tab-pane" id="search">
|
||||
|
||||
{% render_field form.nat_vrf %}
|
||||
</div>
|
||||
</div>
|
||||
{% render_field form.nat_inside %}
|
||||
|
@ -8,7 +8,6 @@
|
||||
{% render_field form.name %}
|
||||
{% render_field form.type %}
|
||||
{% render_field form.group %}
|
||||
{% render_field form.tenant %}
|
||||
{% render_field form.site %}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -7,9 +7,6 @@ from django.urls import reverse
|
||||
|
||||
from .views import server_error
|
||||
|
||||
BASE_PATH = getattr(settings, 'BASE_PATH', False)
|
||||
LOGIN_REQUIRED = getattr(settings, 'LOGIN_REQUIRED', False)
|
||||
|
||||
|
||||
class LoginRequiredMiddleware(object):
|
||||
"""
|
||||
@ -19,7 +16,7 @@ class LoginRequiredMiddleware(object):
|
||||
self.get_response = get_response
|
||||
|
||||
def __call__(self, request):
|
||||
if LOGIN_REQUIRED and not request.user.is_authenticated:
|
||||
if settings.LOGIN_REQUIRED and not request.user.is_authenticated:
|
||||
# Redirect unauthenticated requests to the login page. API requests are exempt from redirection as the API
|
||||
# performs its own authentication. Also metrics can be read without login.
|
||||
api_path = reverse('api-root')
|
||||
|
@ -1,170 +0,0 @@
|
||||
[
|
||||
{
|
||||
"model": "virtualization.clustertype",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"created": "2016-08-01",
|
||||
"last_updated": "2016-08-01T15:22:42.289Z",
|
||||
"name": "Public Cloud",
|
||||
"slug": "public-cloud"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "virtualization.clustertype",
|
||||
"pk": 2,
|
||||
"fields": {
|
||||
"created": "2016-08-01",
|
||||
"last_updated": "2016-08-01T15:22:42.289Z",
|
||||
"name": "vSphere",
|
||||
"slug": "vsphere"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "virtualization.clustertype",
|
||||
"pk": 3,
|
||||
"fields": {
|
||||
"created": "2016-08-01",
|
||||
"last_updated": "2016-08-01T15:22:42.289Z",
|
||||
"name": "Hyper-V",
|
||||
"slug": "hyper-v"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "virtualization.clustertype",
|
||||
"pk": 4,
|
||||
"fields": {
|
||||
"created": "2016-08-01",
|
||||
"last_updated": "2016-08-01T15:22:42.289Z",
|
||||
"name": "libvirt",
|
||||
"slug": "libvirt"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "virtualization.clustertype",
|
||||
"pk": 5,
|
||||
"fields": {
|
||||
"created": "2016-08-01",
|
||||
"last_updated": "2016-08-01T15:22:42.289Z",
|
||||
"name": "LXD",
|
||||
"slug": "lxd"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "virtualization.clustertype",
|
||||
"pk": 6,
|
||||
"fields": {
|
||||
"created": "2016-08-01",
|
||||
"last_updated": "2016-08-01T15:22:42.289Z",
|
||||
"name": "Docker",
|
||||
"slug": "docker"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "virtualization.clustergroup",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"created": "2016-08-01",
|
||||
"last_updated": "2016-08-01T15:22:42.289Z",
|
||||
"name": "VM Host",
|
||||
"slug": "vm-host"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "virtualization.cluster",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"created": "2016-08-01",
|
||||
"last_updated": "2016-08-01T15:22:42.289Z",
|
||||
"name": "Digital Ocean",
|
||||
"type": 1,
|
||||
"group": 1,
|
||||
"tenant": null,
|
||||
"site": null,
|
||||
"comments": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "virtualization.cluster",
|
||||
"pk": 2,
|
||||
"fields": {
|
||||
"created": "2016-08-01",
|
||||
"last_updated": "2016-08-01T15:22:42.289Z",
|
||||
"name": "Amazon EC2",
|
||||
"type": 1,
|
||||
"group": 1,
|
||||
"tenant": null,
|
||||
"site": null,
|
||||
"comments": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "virtualization.cluster",
|
||||
"pk": 3,
|
||||
"fields": {
|
||||
"created": "2016-08-01",
|
||||
"last_updated": "2016-08-01T15:22:42.289Z",
|
||||
"name": "Microsoft Azure",
|
||||
"type": 1,
|
||||
"group": 1,
|
||||
"tenant": null,
|
||||
"site": null,
|
||||
"comments": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "virtualization.cluster",
|
||||
"pk": 4,
|
||||
"fields": {
|
||||
"created": "2016-08-01",
|
||||
"last_updated": "2016-08-01T15:22:42.289Z",
|
||||
"name": "vSphere Cluster",
|
||||
"type": 2,
|
||||
"group": 1,
|
||||
"tenant": null,
|
||||
"site": null,
|
||||
"comments": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "virtualization.virtualmachine",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"local_context_data": null,
|
||||
"created": "2019-12-19",
|
||||
"last_updated": "2019-12-19T05:24:19.146Z",
|
||||
"cluster": 2,
|
||||
"tenant": null,
|
||||
"platform": null,
|
||||
"name": "vm1",
|
||||
"status": "active",
|
||||
"role": null,
|
||||
"primary_ip4": null,
|
||||
"primary_ip6": null,
|
||||
"vcpus": null,
|
||||
"memory": null,
|
||||
"disk": null,
|
||||
"comments": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "virtualization.virtualmachine",
|
||||
"pk": 2,
|
||||
"fields": {
|
||||
"local_context_data": null,
|
||||
"created": "2019-12-19",
|
||||
"last_updated": "2019-12-19T05:24:41.478Z",
|
||||
"cluster": 1,
|
||||
"tenant": null,
|
||||
"platform": null,
|
||||
"name": "vm2",
|
||||
"status": "active",
|
||||
"role": null,
|
||||
"primary_ip4": null,
|
||||
"primary_ip6": null,
|
||||
"vcpus": null,
|
||||
"memory": null,
|
||||
"disk": null,
|
||||
"comments": ""
|
||||
}
|
||||
}
|
||||
]
|
@ -171,7 +171,8 @@ class ClusterBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEdit
|
||||
)
|
||||
)
|
||||
comments = CommentField(
|
||||
widget=SmallTextarea()
|
||||
widget=SmallTextarea,
|
||||
label='Comments'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
@ -536,7 +537,8 @@ class VirtualMachineBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldB
|
||||
label='Disk (GB)'
|
||||
)
|
||||
comments = CommentField(
|
||||
widget=SmallTextarea()
|
||||
widget=SmallTextarea,
|
||||
label='Comments'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
Reference in New Issue
Block a user