mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Introduce the Cloud model
This commit is contained in:
@ -1,16 +1,29 @@
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from circuits.models import Circuit, CircuitTermination, CircuitType, Provider
|
from circuits.models import *
|
||||||
from netbox.api import WritableNestedSerializer
|
from netbox.api import WritableNestedSerializer
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'NestedCircuitSerializer',
|
'NestedCircuitSerializer',
|
||||||
'NestedCircuitTerminationSerializer',
|
'NestedCircuitTerminationSerializer',
|
||||||
'NestedCircuitTypeSerializer',
|
'NestedCircuitTypeSerializer',
|
||||||
|
'NestedCloudSerializer',
|
||||||
'NestedProviderSerializer',
|
'NestedProviderSerializer',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Clouds
|
||||||
|
#
|
||||||
|
|
||||||
|
class NestedCloudSerializer(WritableNestedSerializer):
|
||||||
|
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:cloud-detail')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Provider
|
||||||
|
fields = ['id', 'url', 'display', 'name']
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Providers
|
# Providers
|
||||||
#
|
#
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from circuits.choices import CircuitStatusChoices
|
from circuits.choices import CircuitStatusChoices
|
||||||
from circuits.models import Provider, Circuit, CircuitTermination, CircuitType
|
from circuits.models import *
|
||||||
from dcim.api.nested_serializers import NestedCableSerializer, NestedSiteSerializer
|
from dcim.api.nested_serializers import NestedCableSerializer, NestedSiteSerializer
|
||||||
from dcim.api.serializers import CableTerminationSerializer, ConnectedEndpointSerializer
|
from dcim.api.serializers import CableTerminationSerializer, ConnectedEndpointSerializer
|
||||||
from netbox.api import ChoiceField
|
from netbox.api import ChoiceField
|
||||||
@ -28,6 +28,22 @@ class ProviderSerializer(PrimaryModelSerializer):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Clouds
|
||||||
|
#
|
||||||
|
|
||||||
|
class CloudSerializer(PrimaryModelSerializer):
|
||||||
|
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:cloud-detail')
|
||||||
|
provider = NestedProviderSerializer()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Cloud
|
||||||
|
fields = [
|
||||||
|
'id', 'url', 'display', 'provider', 'name', 'description', 'comments', 'tags', 'custom_fields', 'created',
|
||||||
|
'last_updated',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Circuits
|
# Circuits
|
||||||
#
|
#
|
||||||
|
@ -13,5 +13,8 @@ router.register('circuit-types', views.CircuitTypeViewSet)
|
|||||||
router.register('circuits', views.CircuitViewSet)
|
router.register('circuits', views.CircuitViewSet)
|
||||||
router.register('circuit-terminations', views.CircuitTerminationViewSet)
|
router.register('circuit-terminations', views.CircuitTerminationViewSet)
|
||||||
|
|
||||||
|
# Clouds
|
||||||
|
router.register('clouds', views.CloudViewSet)
|
||||||
|
|
||||||
app_name = 'circuits-api'
|
app_name = 'circuits-api'
|
||||||
urlpatterns = router.urls
|
urlpatterns = router.urls
|
||||||
|
@ -2,7 +2,7 @@ from django.db.models import Prefetch
|
|||||||
from rest_framework.routers import APIRootView
|
from rest_framework.routers import APIRootView
|
||||||
|
|
||||||
from circuits import filters
|
from circuits import filters
|
||||||
from circuits.models import Provider, CircuitTermination, CircuitType, Circuit
|
from circuits.models import *
|
||||||
from dcim.api.views import PathEndpointMixin
|
from dcim.api.views import PathEndpointMixin
|
||||||
from extras.api.views import CustomFieldModelViewSet
|
from extras.api.views import CustomFieldModelViewSet
|
||||||
from netbox.api.views import ModelViewSet
|
from netbox.api.views import ModelViewSet
|
||||||
@ -66,3 +66,13 @@ class CircuitTerminationViewSet(PathEndpointMixin, ModelViewSet):
|
|||||||
serializer_class = serializers.CircuitTerminationSerializer
|
serializer_class = serializers.CircuitTerminationSerializer
|
||||||
filterset_class = filters.CircuitTerminationFilterSet
|
filterset_class = filters.CircuitTerminationFilterSet
|
||||||
brief_prefetch_fields = ['circuit']
|
brief_prefetch_fields = ['circuit']
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Clouds
|
||||||
|
#
|
||||||
|
|
||||||
|
class CloudViewSet(CustomFieldModelViewSet):
|
||||||
|
queryset = Cloud.objects.prefetch_related('tags')
|
||||||
|
serializer_class = serializers.CloudSerializer
|
||||||
|
filterset_class = filters.CloudFilterSet
|
||||||
|
@ -9,12 +9,13 @@ from utilities.filters import (
|
|||||||
BaseFilterSet, NameSlugSearchFilterSet, TagFilter, TreeNodeMultipleChoiceFilter
|
BaseFilterSet, NameSlugSearchFilterSet, TagFilter, TreeNodeMultipleChoiceFilter
|
||||||
)
|
)
|
||||||
from .choices import *
|
from .choices import *
|
||||||
from .models import Circuit, CircuitTermination, CircuitType, Provider
|
from .models import *
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'CircuitFilterSet',
|
'CircuitFilterSet',
|
||||||
'CircuitTerminationFilterSet',
|
'CircuitTerminationFilterSet',
|
||||||
'CircuitTypeFilterSet',
|
'CircuitTypeFilterSet',
|
||||||
|
'CloudFilterSet',
|
||||||
'ProviderFilterSet',
|
'ProviderFilterSet',
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -79,6 +80,36 @@ class ProviderFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdated
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CloudFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
|
||||||
|
q = django_filters.CharFilter(
|
||||||
|
method='search',
|
||||||
|
label='Search',
|
||||||
|
)
|
||||||
|
provider_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
queryset=Provider.objects.all(),
|
||||||
|
label='Provider (ID)',
|
||||||
|
)
|
||||||
|
provider = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='provider__slug',
|
||||||
|
queryset=Provider.objects.all(),
|
||||||
|
to_field_name='slug',
|
||||||
|
label='Provider (slug)',
|
||||||
|
)
|
||||||
|
tag = TagFilter()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Cloud
|
||||||
|
fields = ['id', 'name']
|
||||||
|
|
||||||
|
def search(self, queryset, name, value):
|
||||||
|
if not value.strip():
|
||||||
|
return queryset
|
||||||
|
return queryset.filter(
|
||||||
|
Q(description__icontains=value) |
|
||||||
|
Q(comments__icontains=value)
|
||||||
|
).distinct()
|
||||||
|
|
||||||
|
|
||||||
class CircuitTypeFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
|
class CircuitTypeFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -14,7 +14,7 @@ from utilities.forms import (
|
|||||||
StaticSelect2, StaticSelect2Multiple, TagFilterField,
|
StaticSelect2, StaticSelect2Multiple, TagFilterField,
|
||||||
)
|
)
|
||||||
from .choices import CircuitStatusChoices
|
from .choices import CircuitStatusChoices
|
||||||
from .models import Circuit, CircuitTermination, CircuitType, Provider
|
from .models import *
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -128,6 +128,83 @@ class ProviderFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
tag = TagFilterField(model)
|
tag = TagFilterField(model)
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Clouds
|
||||||
|
#
|
||||||
|
|
||||||
|
class CloudForm(BootstrapMixin, CustomFieldModelForm):
|
||||||
|
provider = DynamicModelChoiceField(
|
||||||
|
queryset=Provider.objects.all()
|
||||||
|
)
|
||||||
|
comments = CommentField()
|
||||||
|
tags = DynamicModelMultipleChoiceField(
|
||||||
|
queryset=Tag.objects.all(),
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Cloud
|
||||||
|
fields = [
|
||||||
|
'provider', 'name', 'description', 'comments', 'tags',
|
||||||
|
]
|
||||||
|
fieldsets = (
|
||||||
|
('Cloud', ('provider', 'name', 'description', 'tags')),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CloudCSVForm(CustomFieldModelCSVForm):
|
||||||
|
provider = CSVModelChoiceField(
|
||||||
|
queryset=Provider.objects.all(),
|
||||||
|
to_field_name='name',
|
||||||
|
help_text='Assigned provider'
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Cloud
|
||||||
|
fields = [
|
||||||
|
'provider', 'name', 'description', 'comments',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class CloudBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm):
|
||||||
|
pk = forms.ModelMultipleChoiceField(
|
||||||
|
queryset=Cloud.objects.all(),
|
||||||
|
widget=forms.MultipleHiddenInput
|
||||||
|
)
|
||||||
|
provider = DynamicModelChoiceField(
|
||||||
|
queryset=Provider.objects.all(),
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
description = forms.CharField(
|
||||||
|
max_length=100,
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
comments = CommentField(
|
||||||
|
widget=SmallTextarea,
|
||||||
|
label='Comments'
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
nullable_fields = [
|
||||||
|
'description', 'comments',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class CloudFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||||
|
model = Cloud
|
||||||
|
field_order = ['q', 'provider_id']
|
||||||
|
q = forms.CharField(
|
||||||
|
required=False,
|
||||||
|
label=_('Search')
|
||||||
|
)
|
||||||
|
provider_id = DynamicModelMultipleChoiceField(
|
||||||
|
queryset=Provider.objects.all(),
|
||||||
|
required=False,
|
||||||
|
label=_('Provider')
|
||||||
|
)
|
||||||
|
tag = TagFilterField(model)
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Circuit types
|
# Circuit types
|
||||||
#
|
#
|
||||||
|
40
netbox/circuits/migrations/0027_cloud.py
Normal file
40
netbox/circuits/migrations/0027_cloud.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import django.core.serializers.json
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import taggit.managers
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('extras', '0058_journalentry'),
|
||||||
|
('circuits', '0026_mark_connected'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Cloud',
|
||||||
|
fields=[
|
||||||
|
('created', models.DateField(auto_now_add=True, null=True)),
|
||||||
|
('last_updated', models.DateTimeField(auto_now=True, null=True)),
|
||||||
|
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)),
|
||||||
|
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
||||||
|
('name', models.CharField(max_length=100)),
|
||||||
|
('description', models.CharField(blank=True, max_length=200)),
|
||||||
|
('comments', models.TextField(blank=True)),
|
||||||
|
('provider', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='clouds', to='circuits.provider')),
|
||||||
|
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'ordering': ('provider', 'name'),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name='cloud',
|
||||||
|
constraint=models.UniqueConstraint(fields=('provider', 'name'), name='circuits_cloud_provider_name'),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='cloud',
|
||||||
|
unique_together={('provider', 'name')},
|
||||||
|
),
|
||||||
|
]
|
@ -15,6 +15,7 @@ __all__ = (
|
|||||||
'Circuit',
|
'Circuit',
|
||||||
'CircuitTermination',
|
'CircuitTermination',
|
||||||
'CircuitType',
|
'CircuitType',
|
||||||
|
'Cloud',
|
||||||
'Provider',
|
'Provider',
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -91,6 +92,59 @@ class Provider(PrimaryModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Clouds
|
||||||
|
#
|
||||||
|
|
||||||
|
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
||||||
|
class Cloud(PrimaryModel):
|
||||||
|
name = models.CharField(
|
||||||
|
max_length=100
|
||||||
|
)
|
||||||
|
provider = models.ForeignKey(
|
||||||
|
to='circuits.Provider',
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
related_name='clouds'
|
||||||
|
)
|
||||||
|
description = models.CharField(
|
||||||
|
max_length=200,
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
comments = models.TextField(
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
|
||||||
|
csv_headers = [
|
||||||
|
'provider', 'name', 'description', 'comments',
|
||||||
|
]
|
||||||
|
|
||||||
|
objects = RestrictedQuerySet.as_manager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ('provider', 'name')
|
||||||
|
constraints = (
|
||||||
|
models.UniqueConstraint(
|
||||||
|
fields=('provider', 'name'),
|
||||||
|
name='circuits_cloud_provider_name'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
unique_together = ('provider', 'name')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse('circuits:cloud', args=[self.pk])
|
||||||
|
|
||||||
|
def to_csv(self):
|
||||||
|
return (
|
||||||
|
self.provider.name,
|
||||||
|
self.name,
|
||||||
|
self.description,
|
||||||
|
self.comments,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
||||||
class CircuitType(OrganizationalModel):
|
class CircuitType(OrganizationalModel):
|
||||||
"""
|
"""
|
||||||
|
@ -3,7 +3,7 @@ from django_tables2.utils import Accessor
|
|||||||
|
|
||||||
from tenancy.tables import TenantColumn
|
from tenancy.tables import TenantColumn
|
||||||
from utilities.tables import BaseTable, ButtonsColumn, ChoiceFieldColumn, TagColumn, ToggleColumn
|
from utilities.tables import BaseTable, ButtonsColumn, ChoiceFieldColumn, TagColumn, ToggleColumn
|
||||||
from .models import Circuit, CircuitType, Provider
|
from .models import *
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -29,6 +29,28 @@ class ProviderTable(BaseTable):
|
|||||||
default_columns = ('pk', 'name', 'asn', 'account', 'circuit_count')
|
default_columns = ('pk', 'name', 'asn', 'account', 'circuit_count')
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Clouds
|
||||||
|
#
|
||||||
|
|
||||||
|
class CloudTable(BaseTable):
|
||||||
|
pk = ToggleColumn()
|
||||||
|
name = tables.Column(
|
||||||
|
linkify=True
|
||||||
|
)
|
||||||
|
provider = tables.Column(
|
||||||
|
linkify=True
|
||||||
|
)
|
||||||
|
tags = TagColumn(
|
||||||
|
url_name='circuits:cloud_list'
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta(BaseTable.Meta):
|
||||||
|
model = Cloud
|
||||||
|
fields = ('pk', 'name', 'provider', 'description', 'tags')
|
||||||
|
default_columns = ('pk', 'name', 'provider', 'description')
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Circuit types
|
# Circuit types
|
||||||
#
|
#
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from circuits.choices import *
|
from circuits.choices import *
|
||||||
from circuits.models import Circuit, CircuitTermination, CircuitType, Provider
|
from circuits.models import *
|
||||||
from dcim.models import Site
|
from dcim.models import Site
|
||||||
from utilities.testing import APITestCase, APIViewTestCases
|
from utilities.testing import APITestCase, APIViewTestCases
|
||||||
|
|
||||||
@ -178,3 +178,43 @@ class CircuitTerminationTest(APIViewTestCases.APIViewTestCase):
|
|||||||
cls.bulk_update_data = {
|
cls.bulk_update_data = {
|
||||||
'port_speed': 123456
|
'port_speed': 123456
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class CloudTest(APIViewTestCases.APIViewTestCase):
|
||||||
|
model = Cloud
|
||||||
|
brief_fields = ['display', 'id', 'name', 'url']
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
providers = (
|
||||||
|
Provider(name='Provider 1', slug='provider-1'),
|
||||||
|
Provider(name='Provider 2', slug='provider-2'),
|
||||||
|
)
|
||||||
|
Provider.objects.bulk_create(providers)
|
||||||
|
|
||||||
|
clouds = (
|
||||||
|
Cloud(name='Cloud 1', provider=providers[0]),
|
||||||
|
Cloud(name='Cloud 2', provider=providers[0]),
|
||||||
|
Cloud(name='Cloud 3', provider=providers[0]),
|
||||||
|
)
|
||||||
|
Cloud.objects.bulk_create(clouds)
|
||||||
|
|
||||||
|
cls.create_data = [
|
||||||
|
{
|
||||||
|
'name': 'Cloud 4',
|
||||||
|
'provider': providers[0].pk,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'Cloud 5',
|
||||||
|
'provider': providers[0].pk,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'Cloud 6',
|
||||||
|
'provider': providers[0].pk,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
cls.bulk_update_data = {
|
||||||
|
'provider': providers[1].pk,
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
|
@ -2,7 +2,7 @@ from django.test import TestCase
|
|||||||
|
|
||||||
from circuits.choices import *
|
from circuits.choices import *
|
||||||
from circuits.filters import *
|
from circuits.filters import *
|
||||||
from circuits.models import Circuit, CircuitTermination, CircuitType, Provider
|
from circuits.models import *
|
||||||
from dcim.models import Cable, Region, Site, SiteGroup
|
from dcim.models import Cable, Region, Site, SiteGroup
|
||||||
from tenancy.models import Tenant, TenantGroup
|
from tenancy.models import Tenant, TenantGroup
|
||||||
|
|
||||||
@ -353,3 +353,40 @@ class CircuitTerminationTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
params = {'connected': False}
|
params = {'connected': False}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||||
|
|
||||||
|
|
||||||
|
class CloudTestCase(TestCase):
|
||||||
|
queryset = Cloud.objects.all()
|
||||||
|
filterset = CloudFilterSet
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
|
||||||
|
providers = (
|
||||||
|
Provider(name='Provider 1', slug='provider-1'),
|
||||||
|
Provider(name='Provider 2', slug='provider-2'),
|
||||||
|
Provider(name='Provider 3', slug='provider-3'),
|
||||||
|
)
|
||||||
|
Provider.objects.bulk_create(providers)
|
||||||
|
|
||||||
|
clouds = (
|
||||||
|
Cloud(name='Cloud 1', provider=providers[0]),
|
||||||
|
Cloud(name='Cloud 2', provider=providers[1]),
|
||||||
|
Cloud(name='Cloud 3', provider=providers[2]),
|
||||||
|
)
|
||||||
|
Cloud.objects.bulk_create(clouds)
|
||||||
|
|
||||||
|
def test_id(self):
|
||||||
|
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
def test_name(self):
|
||||||
|
params = {'name': ['Cloud 1', 'Cloud 2']}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
def test_provider(self):
|
||||||
|
providers = Provider.objects.all()[:2]
|
||||||
|
params = {'provider_id': [providers[0].pk, providers[1].pk]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
params = {'provider': [providers[0].slug, providers[1].slug]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from circuits.choices import *
|
from circuits.choices import *
|
||||||
from circuits.models import Circuit, CircuitType, Provider
|
from circuits.models import *
|
||||||
from utilities.testing import ViewTestCases
|
from utilities.testing import ViewTestCases
|
||||||
|
|
||||||
|
|
||||||
@ -133,3 +133,45 @@ class CircuitTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
'description': 'New description',
|
'description': 'New description',
|
||||||
'comments': 'New comments',
|
'comments': 'New comments',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class CloudTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||||
|
model = Cloud
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
|
||||||
|
providers = (
|
||||||
|
Provider(name='Provider 1', slug='provider-1'),
|
||||||
|
Provider(name='Provider 2', slug='provider-2'),
|
||||||
|
)
|
||||||
|
Provider.objects.bulk_create(providers)
|
||||||
|
|
||||||
|
Cloud.objects.bulk_create([
|
||||||
|
Cloud(name='Cloud 1', provider=providers[0]),
|
||||||
|
Cloud(name='Cloud 2', provider=providers[0]),
|
||||||
|
Cloud(name='Cloud 3', provider=providers[0]),
|
||||||
|
])
|
||||||
|
|
||||||
|
tags = cls.create_tags('Alpha', 'Bravo', 'Charlie')
|
||||||
|
|
||||||
|
cls.form_data = {
|
||||||
|
'name': 'Cloud X',
|
||||||
|
'provider': providers[1].pk,
|
||||||
|
'description': 'A new cloud',
|
||||||
|
'comments': 'Longer description goes here',
|
||||||
|
'tags': [t.pk for t in tags],
|
||||||
|
}
|
||||||
|
|
||||||
|
cls.csv_data = (
|
||||||
|
"name,provider,description",
|
||||||
|
"Cloud 4,Provider 1,Foo",
|
||||||
|
"Cloud 5,Provider 1,Bar",
|
||||||
|
"Cloud 6,Provider 1,Baz",
|
||||||
|
)
|
||||||
|
|
||||||
|
cls.bulk_edit_data = {
|
||||||
|
'provider': providers[1].pk,
|
||||||
|
'description': 'New description',
|
||||||
|
'comments': 'New comments',
|
||||||
|
}
|
||||||
|
@ -3,7 +3,7 @@ from django.urls import path
|
|||||||
from dcim.views import CableCreateView, PathTraceView
|
from dcim.views import CableCreateView, PathTraceView
|
||||||
from extras.views import ObjectChangeLogView, ObjectJournalView
|
from extras.views import ObjectChangeLogView, ObjectJournalView
|
||||||
from . import views
|
from . import views
|
||||||
from .models import Circuit, CircuitTermination, CircuitType, Provider
|
from .models import *
|
||||||
|
|
||||||
app_name = 'circuits'
|
app_name = 'circuits'
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
@ -20,6 +20,18 @@ urlpatterns = [
|
|||||||
path('providers/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='provider_changelog', kwargs={'model': Provider}),
|
path('providers/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='provider_changelog', kwargs={'model': Provider}),
|
||||||
path('providers/<int:pk>/journal/', ObjectJournalView.as_view(), name='provider_journal', kwargs={'model': Provider}),
|
path('providers/<int:pk>/journal/', ObjectJournalView.as_view(), name='provider_journal', kwargs={'model': Provider}),
|
||||||
|
|
||||||
|
# Clouds
|
||||||
|
path('clouds/', views.CloudListView.as_view(), name='cloud_list'),
|
||||||
|
path('clouds/add/', views.CloudEditView.as_view(), name='cloud_add'),
|
||||||
|
path('clouds/import/', views.CloudBulkImportView.as_view(), name='cloud_import'),
|
||||||
|
path('clouds/edit/', views.CloudBulkEditView.as_view(), name='cloud_bulk_edit'),
|
||||||
|
path('clouds/delete/', views.CloudBulkDeleteView.as_view(), name='cloud_bulk_delete'),
|
||||||
|
path('clouds/<int:pk>/', views.CloudView.as_view(), name='cloud'),
|
||||||
|
path('clouds/<int:pk>/edit/', views.CloudEditView.as_view(), name='cloud_edit'),
|
||||||
|
path('clouds/<int:pk>/delete/', views.CloudDeleteView.as_view(), name='cloud_delete'),
|
||||||
|
path('clouds/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='cloud_changelog', kwargs={'model': Cloud}),
|
||||||
|
path('clouds/<int:pk>/journal/', ObjectJournalView.as_view(), name='cloud_journal', kwargs={'model': Cloud}),
|
||||||
|
|
||||||
# Circuit types
|
# Circuit types
|
||||||
path('circuit-types/', views.CircuitTypeListView.as_view(), name='circuittype_list'),
|
path('circuit-types/', views.CircuitTypeListView.as_view(), name='circuittype_list'),
|
||||||
path('circuit-types/add/', views.CircuitTypeEditView.as_view(), name='circuittype_add'),
|
path('circuit-types/add/', views.CircuitTypeEditView.as_view(), name='circuittype_add'),
|
||||||
|
@ -9,7 +9,7 @@ from utilities.paginator import EnhancedPaginator, get_paginate_count
|
|||||||
from utilities.utils import count_related
|
from utilities.utils import count_related
|
||||||
from . import filters, forms, tables
|
from . import filters, forms, tables
|
||||||
from .choices import CircuitTerminationSideChoices
|
from .choices import CircuitTerminationSideChoices
|
||||||
from .models import Circuit, CircuitTermination, CircuitType, Provider
|
from .models import *
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -81,6 +81,49 @@ class ProviderBulkDeleteView(generic.BulkDeleteView):
|
|||||||
table = tables.ProviderTable
|
table = tables.ProviderTable
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Clouds
|
||||||
|
#
|
||||||
|
|
||||||
|
class CloudListView(generic.ObjectListView):
|
||||||
|
queryset = Cloud.objects.all()
|
||||||
|
filterset = filters.CloudFilterSet
|
||||||
|
filterset_form = forms.CloudFilterForm
|
||||||
|
table = tables.CloudTable
|
||||||
|
|
||||||
|
|
||||||
|
class CloudView(generic.ObjectView):
|
||||||
|
queryset = Cloud.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class CloudEditView(generic.ObjectEditView):
|
||||||
|
queryset = Cloud.objects.all()
|
||||||
|
model_form = forms.CloudForm
|
||||||
|
|
||||||
|
|
||||||
|
class CloudDeleteView(generic.ObjectDeleteView):
|
||||||
|
queryset = Cloud.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class CloudBulkImportView(generic.BulkImportView):
|
||||||
|
queryset = Cloud.objects.all()
|
||||||
|
model_form = forms.CloudCSVForm
|
||||||
|
table = tables.CloudTable
|
||||||
|
|
||||||
|
|
||||||
|
class CloudBulkEditView(generic.BulkEditView):
|
||||||
|
queryset = Cloud.objects.all()
|
||||||
|
filterset = filters.CloudFilterSet
|
||||||
|
table = tables.CloudTable
|
||||||
|
form = forms.CloudBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
class CloudBulkDeleteView(generic.BulkDeleteView):
|
||||||
|
queryset = Cloud.objects.all()
|
||||||
|
filterset = filters.CloudFilterSet
|
||||||
|
table = tables.CloudTable
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Circuit Types
|
# Circuit Types
|
||||||
#
|
#
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from django.db.models import Count
|
from circuits.filters import CircuitFilterSet, CloudFilterSet, ProviderFilterSet
|
||||||
|
from circuits.models import Circuit, Cloud, Provider
|
||||||
from circuits.filters import CircuitFilterSet, ProviderFilterSet
|
from circuits.tables import CircuitTable, CloudTable, ProviderTable
|
||||||
from circuits.models import Circuit, Provider
|
|
||||||
from circuits.tables import CircuitTable, ProviderTable
|
|
||||||
from dcim.filters import (
|
from dcim.filters import (
|
||||||
CableFilterSet, DeviceFilterSet, DeviceTypeFilterSet, PowerFeedFilterSet, RackFilterSet, LocationFilterSet,
|
CableFilterSet, DeviceFilterSet, DeviceTypeFilterSet, PowerFeedFilterSet, RackFilterSet, LocationFilterSet,
|
||||||
SiteFilterSet, VirtualChassisFilterSet,
|
SiteFilterSet, VirtualChassisFilterSet,
|
||||||
@ -47,6 +45,12 @@ SEARCH_TYPES = OrderedDict((
|
|||||||
'table': CircuitTable,
|
'table': CircuitTable,
|
||||||
'url': 'circuits:circuit_list',
|
'url': 'circuits:circuit_list',
|
||||||
}),
|
}),
|
||||||
|
('cloud', {
|
||||||
|
'queryset': Cloud.objects.prefetch_related('provider'),
|
||||||
|
'filterset': CloudFilterSet,
|
||||||
|
'table': CloudTable,
|
||||||
|
'url': 'circuits:cloud_list',
|
||||||
|
}),
|
||||||
# DCIM
|
# DCIM
|
||||||
('site', {
|
('site', {
|
||||||
'queryset': Site.objects.prefetch_related('region', 'tenant'),
|
'queryset': Site.objects.prefetch_related('region', 'tenant'),
|
||||||
|
55
netbox/templates/circuits/cloud.html
Normal file
55
netbox/templates/circuits/cloud.html
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
{% extends 'generic/object.html' %}
|
||||||
|
{% load static %}
|
||||||
|
{% load helpers %}
|
||||||
|
{% load plugins %}
|
||||||
|
|
||||||
|
{% block breadcrumbs %}
|
||||||
|
<li><a href="{% url 'circuits:cloud_list' %}">Clouds</a></li>
|
||||||
|
<li><a href="{% url 'circuits:cloud_list' %}?provider_id={{ object.provider_id }}">{{ object.provider }}</a></li>
|
||||||
|
<li>{{ object }}</li>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<strong>Cloud</strong>
|
||||||
|
</div>
|
||||||
|
<table class="table table-hover panel-body attr-table">
|
||||||
|
<tr>
|
||||||
|
<td>Name</td>
|
||||||
|
<td>{{ object.name }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Description</td>
|
||||||
|
<td>{{ object.description }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% include 'inc/custom_fields_panel.html' %}
|
||||||
|
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='circuits:cloud_list' %}
|
||||||
|
{% plugin_left_page object %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-8">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<strong>Comments</strong>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body rendered-markdown">
|
||||||
|
{% if object.comments %}
|
||||||
|
{{ object.comments|render_markdown }}
|
||||||
|
{% else %}
|
||||||
|
<span class="text-muted">None</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% plugin_right_page object %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
{% plugin_full_width_page object %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
@ -465,6 +465,14 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{% url 'circuits:provider_list' %}">Providers</a>
|
<a href="{% url 'circuits:provider_list' %}">Providers</a>
|
||||||
|
<li{% if not perms.circuits.view_cloud %} class="disabled"{% endif %}>
|
||||||
|
{% if perms.circuits.add_cloud %}
|
||||||
|
<div class="buttons pull-right">
|
||||||
|
<a href="{% url 'circuits:cloud_add' %}" class="btn btn-xs btn-success" title="Add"><i class="mdi mdi-plus-thick"></i></a>
|
||||||
|
<a href="{% url 'circuits:cloud_import' %}" class="btn btn-xs btn-info" title="Import"><i class="mdi mdi-database-import-outline"></i></a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<a href="{% url 'circuits:cloud_list' %}">Clouds</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
Reference in New Issue
Block a user