mirror of
https://github.com/netbox-community/netbox.git
synced 2025-12-21 21:02:23 -06:00
Merge branch 'main' into feature
This commit is contained in:
@@ -12,7 +12,7 @@ from django.shortcuts import get_object_or_404, redirect
|
||||
from django.shortcuts import render, resolve_url
|
||||
from django.urls import reverse
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.http import url_has_allowed_host_and_scheme, urlencode
|
||||
from django.utils.http import urlencode
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views.decorators.debug import sensitive_post_parameters
|
||||
from django.views.generic import View
|
||||
@@ -28,6 +28,7 @@ from netbox.config import get_config
|
||||
from netbox.views import generic
|
||||
from users import forms, tables
|
||||
from users.models import UserConfig
|
||||
from utilities.request import safe_for_redirect
|
||||
from utilities.string import remove_linebreaks
|
||||
from utilities.views import register_model_view
|
||||
|
||||
@@ -148,7 +149,7 @@ class LoginView(View):
|
||||
data = request.POST if request.method == "POST" else request.GET
|
||||
redirect_url = data.get('next', settings.LOGIN_REDIRECT_URL)
|
||||
|
||||
if redirect_url and url_has_allowed_host_and_scheme(redirect_url, allowed_hosts=None):
|
||||
if redirect_url and safe_for_redirect(redirect_url):
|
||||
logger.debug(f"Redirecting user to {remove_linebreaks(redirect_url)}")
|
||||
else:
|
||||
if redirect_url:
|
||||
|
||||
@@ -61,9 +61,8 @@ class CircuitTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
|
||||
linkify=True,
|
||||
verbose_name=_('Account')
|
||||
)
|
||||
type = tables.Column(
|
||||
type = columns.ColoredLabelColumn(
|
||||
verbose_name=_('Type'),
|
||||
linkify=True
|
||||
)
|
||||
status = columns.ChoiceFieldColumn()
|
||||
termination_a = columns.TemplateColumn(
|
||||
|
||||
@@ -35,7 +35,19 @@ class ProviderView(GetRelatedModelsMixin, generic.ObjectView):
|
||||
|
||||
def get_extra_context(self, request, instance):
|
||||
return {
|
||||
'related_models': self.get_related_models(request, instance),
|
||||
'related_models': self.get_related_models(
|
||||
request,
|
||||
instance,
|
||||
omit=(),
|
||||
extra=(
|
||||
(
|
||||
VirtualCircuit.objects.restrict(request.user, 'view').filter(
|
||||
provider_network__provider=instance
|
||||
),
|
||||
'provider_id',
|
||||
),
|
||||
),
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +63,7 @@ class ProviderDeleteView(generic.ObjectDeleteView):
|
||||
queryset = Provider.objects.all()
|
||||
|
||||
|
||||
@register_model_view(Provider, 'bulk_import', detail=False)
|
||||
@register_model_view(Provider, 'bulk_import', path='import', detail=False)
|
||||
class ProviderBulkImportView(generic.BulkImportView):
|
||||
queryset = Provider.objects.all()
|
||||
model_form = forms.ProviderImportForm
|
||||
@@ -112,7 +124,7 @@ class ProviderAccountDeleteView(generic.ObjectDeleteView):
|
||||
queryset = ProviderAccount.objects.all()
|
||||
|
||||
|
||||
@register_model_view(ProviderAccount, 'bulk_import', detail=False)
|
||||
@register_model_view(ProviderAccount, 'bulk_import', path='import', detail=False)
|
||||
class ProviderAccountBulkImportView(generic.BulkImportView):
|
||||
queryset = ProviderAccount.objects.all()
|
||||
model_form = forms.ProviderAccountImportForm
|
||||
@@ -186,7 +198,7 @@ class ProviderNetworkDeleteView(generic.ObjectDeleteView):
|
||||
queryset = ProviderNetwork.objects.all()
|
||||
|
||||
|
||||
@register_model_view(ProviderNetwork, 'bulk_import', detail=False)
|
||||
@register_model_view(ProviderNetwork, 'bulk_import', path='import', detail=False)
|
||||
class ProviderNetworkBulkImportView(generic.BulkImportView):
|
||||
queryset = ProviderNetwork.objects.all()
|
||||
model_form = forms.ProviderNetworkImportForm
|
||||
@@ -243,7 +255,7 @@ class CircuitTypeDeleteView(generic.ObjectDeleteView):
|
||||
queryset = CircuitType.objects.all()
|
||||
|
||||
|
||||
@register_model_view(CircuitType, 'bulk_import', detail=False)
|
||||
@register_model_view(CircuitType, 'bulk_import', path='import', detail=False)
|
||||
class CircuitTypeBulkImportView(generic.BulkImportView):
|
||||
queryset = CircuitType.objects.all()
|
||||
model_form = forms.CircuitTypeImportForm
|
||||
@@ -299,7 +311,7 @@ class CircuitDeleteView(generic.ObjectDeleteView):
|
||||
queryset = Circuit.objects.all()
|
||||
|
||||
|
||||
@register_model_view(Circuit, 'bulk_import', detail=False)
|
||||
@register_model_view(Circuit, 'bulk_import', path='import', detail=False)
|
||||
class CircuitBulkImportView(generic.BulkImportView):
|
||||
queryset = Circuit.objects.all()
|
||||
model_form = forms.CircuitImportForm
|
||||
@@ -439,7 +451,7 @@ class CircuitTerminationDeleteView(generic.ObjectDeleteView):
|
||||
queryset = CircuitTermination.objects.all()
|
||||
|
||||
|
||||
@register_model_view(CircuitTermination, 'bulk_import', detail=False)
|
||||
@register_model_view(CircuitTermination, 'bulk_import', path='import', detail=False)
|
||||
class CircuitTerminationBulkImportView(generic.BulkImportView):
|
||||
queryset = CircuitTermination.objects.all()
|
||||
model_form = forms.CircuitTerminationImportForm
|
||||
@@ -500,7 +512,7 @@ class CircuitGroupDeleteView(generic.ObjectDeleteView):
|
||||
queryset = CircuitGroup.objects.all()
|
||||
|
||||
|
||||
@register_model_view(CircuitGroup, 'bulk_import', detail=False)
|
||||
@register_model_view(CircuitGroup, 'bulk_import', path='import', detail=False)
|
||||
class CircuitGroupBulkImportView(generic.BulkImportView):
|
||||
queryset = CircuitGroup.objects.all()
|
||||
model_form = forms.CircuitGroupImportForm
|
||||
@@ -550,7 +562,7 @@ class CircuitGroupAssignmentDeleteView(generic.ObjectDeleteView):
|
||||
queryset = CircuitGroupAssignment.objects.all()
|
||||
|
||||
|
||||
@register_model_view(CircuitGroupAssignment, 'bulk_import', detail=False)
|
||||
@register_model_view(CircuitGroupAssignment, 'bulk_import', path='import', detail=False)
|
||||
class CircuitGroupAssignmentBulkImportView(generic.BulkImportView):
|
||||
queryset = CircuitGroupAssignment.objects.all()
|
||||
model_form = forms.CircuitGroupAssignmentImportForm
|
||||
@@ -607,7 +619,7 @@ class VirtualCircuitTypeDeleteView(generic.ObjectDeleteView):
|
||||
queryset = VirtualCircuitType.objects.all()
|
||||
|
||||
|
||||
@register_model_view(VirtualCircuitType, 'bulk_import', detail=False)
|
||||
@register_model_view(VirtualCircuitType, 'bulk_import', path='import', detail=False)
|
||||
class VirtualCircuitTypeBulkImportView(generic.BulkImportView):
|
||||
queryset = VirtualCircuitType.objects.all()
|
||||
model_form = forms.VirtualCircuitTypeImportForm
|
||||
|
||||
17
netbox/core/migrations/0013_job_data_encoder.py
Normal file
17
netbox/core/migrations/0013_job_data_encoder.py
Normal file
@@ -0,0 +1,17 @@
|
||||
import django.core.serializers.json
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0012_job_object_type_optional'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='job',
|
||||
name='data',
|
||||
field=models.JSONField(blank=True, encoder=django.core.serializers.json.DjangoJSONEncoder, null=True),
|
||||
),
|
||||
]
|
||||
@@ -1,12 +1,10 @@
|
||||
# Generated by Django 5.1.6 on 2025-02-26 19:45
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0012_job_object_type_optional'),
|
||||
('core', '0013_job_data_encoder'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
@@ -1,12 +1,10 @@
|
||||
# Generated by Django 5.2b1 on 2025-04-03 18:32
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0013_datasource_sync_interval'),
|
||||
('core', '0014_datasource_sync_interval'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
@@ -5,6 +5,7 @@ import django_rq
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
from django.core.validators import MinValueValidator
|
||||
from django.db import models, transaction
|
||||
from django.urls import reverse
|
||||
@@ -90,8 +91,9 @@ class Job(models.Model):
|
||||
)
|
||||
data = models.JSONField(
|
||||
verbose_name=_('data'),
|
||||
encoder=DjangoJSONEncoder,
|
||||
null=True,
|
||||
blank=True
|
||||
blank=True,
|
||||
)
|
||||
error = models.TextField(
|
||||
verbose_name=_('error'),
|
||||
|
||||
@@ -49,6 +49,7 @@ class Plugin:
|
||||
The representation of a NetBox plugin in the catalog API.
|
||||
"""
|
||||
id: str = ''
|
||||
icon_url: str = ''
|
||||
status: str = ''
|
||||
title_short: str = ''
|
||||
title_long: str = ''
|
||||
@@ -210,6 +211,7 @@ def get_catalog_plugins():
|
||||
# Populate plugin data
|
||||
plugins[data['config_name']] = Plugin(
|
||||
id=data['id'],
|
||||
icon_url=data['icon'],
|
||||
status=data['status'],
|
||||
title_short=data['title_short'],
|
||||
title_long=data['title_long'],
|
||||
|
||||
@@ -2,7 +2,7 @@ import logging
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db.models.fields.reverse_related import ManyToManyRel
|
||||
from django.db.models.fields.reverse_related import ManyToManyRel, ManyToOneRel
|
||||
from django.db.models.signals import m2m_changed, post_save, pre_delete
|
||||
from django.dispatch import receiver, Signal
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
@@ -145,8 +145,10 @@ def handle_deleted_object(sender, instance, **kwargs):
|
||||
# instance being deleted, and explicitly call .remove() on the remote M2M field to delete
|
||||
# the association. This triggers an m2m_changed signal with the `post_remove` action type
|
||||
# for the forward direction of the relationship, ensuring that the change is recorded.
|
||||
# Similarly, for many-to-one relationships, we set the value on the related object to None
|
||||
# and save it to trigger a change record on that object.
|
||||
for relation in instance._meta.related_objects:
|
||||
if type(relation) is not ManyToManyRel:
|
||||
if type(relation) not in [ManyToManyRel, ManyToOneRel]:
|
||||
continue
|
||||
related_model = relation.related_model
|
||||
related_field_name = relation.remote_field.name
|
||||
@@ -156,7 +158,11 @@ def handle_deleted_object(sender, instance, **kwargs):
|
||||
continue
|
||||
for obj in related_model.objects.filter(**{related_field_name: instance.pk}):
|
||||
obj.snapshot() # Ensure the change record includes the "before" state
|
||||
getattr(obj, related_field_name).remove(instance)
|
||||
if type(relation) is ManyToManyRel:
|
||||
getattr(obj, related_field_name).remove(instance)
|
||||
elif type(relation) is ManyToOneRel and relation.field.null is True:
|
||||
setattr(obj, related_field_name, None)
|
||||
obj.save()
|
||||
|
||||
# Enqueue the object for event processing
|
||||
queue = events_queue.get()
|
||||
|
||||
@@ -12,6 +12,12 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
PLUGIN_NAME_TEMPLATE = """
|
||||
<img class="plugin-icon" src="{{ record.icon_url }}">
|
||||
<a href="{% url 'core:plugin' record.config_name %}">{{ record.title_long }}</a>
|
||||
"""
|
||||
|
||||
|
||||
class PluginVersionTable(BaseTable):
|
||||
version = tables.Column(
|
||||
verbose_name=_('Version')
|
||||
@@ -42,8 +48,9 @@ class PluginVersionTable(BaseTable):
|
||||
|
||||
|
||||
class CatalogPluginTable(BaseTable):
|
||||
title_long = tables.Column(
|
||||
verbose_name=_('Name'),
|
||||
title_long = columns.TemplateColumn(
|
||||
template_code=PLUGIN_NAME_TEMPLATE,
|
||||
verbose_name=_('Name')
|
||||
)
|
||||
author = tables.Column(
|
||||
accessor=tables.A('author__name'),
|
||||
|
||||
@@ -103,7 +103,7 @@ class DataSourceDeleteView(generic.ObjectDeleteView):
|
||||
queryset = DataSource.objects.all()
|
||||
|
||||
|
||||
@register_model_view(DataSource, 'bulk_import', detail=False)
|
||||
@register_model_view(DataSource, 'bulk_import', path='import', detail=False)
|
||||
class DataSourceBulkImportView(generic.BulkImportView):
|
||||
queryset = DataSource.objects.all()
|
||||
model_form = forms.DataSourceImportForm
|
||||
|
||||
@@ -1390,10 +1390,75 @@ class ModuleFilterSet(NetBoxModelFilterSet):
|
||||
lookup_expr='in',
|
||||
label=_('Module bay (ID)'),
|
||||
)
|
||||
region_id = TreeNodeMultipleChoiceFilter(
|
||||
queryset=Region.objects.all(),
|
||||
field_name='device__site__region',
|
||||
lookup_expr='in',
|
||||
label=_('Region (ID)'),
|
||||
)
|
||||
region = TreeNodeMultipleChoiceFilter(
|
||||
queryset=Region.objects.all(),
|
||||
field_name='device__site__region',
|
||||
lookup_expr='in',
|
||||
to_field_name='slug',
|
||||
label=_('Region (slug)'),
|
||||
)
|
||||
site_group_id = TreeNodeMultipleChoiceFilter(
|
||||
queryset=SiteGroup.objects.all(),
|
||||
field_name='device__site__group',
|
||||
lookup_expr='in',
|
||||
label=_('Site group (ID)'),
|
||||
)
|
||||
site_group = TreeNodeMultipleChoiceFilter(
|
||||
queryset=SiteGroup.objects.all(),
|
||||
field_name='device__site__group',
|
||||
lookup_expr='in',
|
||||
to_field_name='slug',
|
||||
label=_('Site group (slug)'),
|
||||
)
|
||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='device__site',
|
||||
queryset=Site.objects.all(),
|
||||
label=_('Site (ID)'),
|
||||
)
|
||||
site = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='device__site__slug',
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='slug',
|
||||
label=_('Site name (slug)'),
|
||||
)
|
||||
location_id = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='device__location',
|
||||
queryset=Location.objects.all(),
|
||||
label=_('Location (ID)'),
|
||||
)
|
||||
location = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='device__location__slug',
|
||||
queryset=Location.objects.all(),
|
||||
to_field_name='slug',
|
||||
label=_('Location (slug)'),
|
||||
)
|
||||
rack_id = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='device__rack',
|
||||
queryset=Rack.objects.all(),
|
||||
label=_('Rack (ID)'),
|
||||
)
|
||||
rack = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='device__rack__name',
|
||||
queryset=Rack.objects.all(),
|
||||
to_field_name='name',
|
||||
label=_('Rack (name)'),
|
||||
)
|
||||
device_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=Device.objects.all(),
|
||||
label=_('Device (ID)'),
|
||||
)
|
||||
device = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='device__name',
|
||||
queryset=Device.objects.all(),
|
||||
to_field_name='name',
|
||||
label=_('Device (name)'),
|
||||
)
|
||||
status = django_filters.MultipleChoiceFilter(
|
||||
choices=ModuleStatusChoices,
|
||||
null_value=None
|
||||
|
||||
@@ -121,11 +121,11 @@ class DeviceBayBulkCreateForm(DeviceBulkAddComponentForm):
|
||||
|
||||
|
||||
class InventoryItemBulkCreateForm(
|
||||
form_from_model(InventoryItem, ['role', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered']),
|
||||
form_from_model(InventoryItem, ['status', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered']),
|
||||
DeviceBulkAddComponentForm
|
||||
):
|
||||
model = InventoryItem
|
||||
field_order = (
|
||||
'name', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered',
|
||||
'name', 'label', 'status', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered',
|
||||
'description', 'tags',
|
||||
)
|
||||
|
||||
@@ -41,7 +41,6 @@ class InterfaceCommonForm(forms.Form):
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
|
||||
parent_field = 'device' if 'device' in self.cleaned_data else 'virtual_machine'
|
||||
if 'tagged_vlans' in self.fields.keys():
|
||||
tagged_vlans = self.cleaned_data.get('tagged_vlans') if self.is_bound else \
|
||||
@@ -61,6 +60,12 @@ class InterfaceCommonForm(forms.Form):
|
||||
"or they must be global"
|
||||
).format(vlans=', '.join(invalid_vlans))
|
||||
})
|
||||
# Validate mode change
|
||||
if self.instance.pk and (self.instance.mode != self.cleaned_data['mode']):
|
||||
if 'untagged_vlan' not in self.cleaned_data and self.instance.untagged_vlan is not None:
|
||||
self.instance.untagged_vlan = None
|
||||
if 'tagged_vlans' not in self.cleaned_data and self.instance.tagged_vlans is not None:
|
||||
self.instance.tagged_vlans.clear()
|
||||
|
||||
|
||||
class ModuleCommonForm(forms.Form):
|
||||
|
||||
@@ -959,8 +959,56 @@ class ModuleFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, NetBoxMo
|
||||
model = Module
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'device_id', name=_('Location')),
|
||||
FieldSet('manufacturer_id', 'module_type_id', 'status', 'serial', 'asset_tag', name=_('Hardware')),
|
||||
)
|
||||
device_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
required=False,
|
||||
query_params={
|
||||
'site_id': '$site_id',
|
||||
'location_id': '$location_id',
|
||||
'rack_id': '$rack_id',
|
||||
},
|
||||
label=_('Device')
|
||||
)
|
||||
region_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
required=False,
|
||||
label=_('Region')
|
||||
)
|
||||
site_group_id = DynamicModelMultipleChoiceField(
|
||||
queryset=SiteGroup.objects.all(),
|
||||
required=False,
|
||||
label=_('Site group')
|
||||
)
|
||||
site_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
required=False,
|
||||
query_params={
|
||||
'region_id': '$region_id',
|
||||
'group_id': '$site_group_id',
|
||||
},
|
||||
label=_('Site')
|
||||
)
|
||||
location_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Location.objects.all(),
|
||||
required=False,
|
||||
query_params={
|
||||
'site_id': '$site_id',
|
||||
},
|
||||
label=_('Location')
|
||||
)
|
||||
rack_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Rack.objects.all(),
|
||||
required=False,
|
||||
label=_('Rack'),
|
||||
null_option='None',
|
||||
query_params={
|
||||
'site_id': '$site_id',
|
||||
'location_id': '$location_id',
|
||||
}
|
||||
)
|
||||
manufacturer_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Manufacturer.objects.all(),
|
||||
required=False,
|
||||
|
||||
@@ -993,7 +993,7 @@ class ComponentTemplateForm(forms.ModelForm):
|
||||
class ModularComponentTemplateForm(ComponentTemplateForm):
|
||||
device_type = DynamicModelChoiceField(
|
||||
label=_('Device type'),
|
||||
queryset=DeviceType.objects.all().all(),
|
||||
queryset=DeviceType.objects.all(),
|
||||
required=False,
|
||||
context={
|
||||
'parent': 'manufacturer',
|
||||
@@ -1008,6 +1008,16 @@ class ModularComponentTemplateForm(ComponentTemplateForm):
|
||||
}
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
FieldSet(
|
||||
TabbedGroups(
|
||||
FieldSet('device_type', name=_('Device Type')),
|
||||
FieldSet('module_type', name=_('Module Type')),
|
||||
),
|
||||
'name', 'label', 'type', 'description'
|
||||
),
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@@ -1024,10 +1034,6 @@ class ModularComponentTemplateForm(ComponentTemplateForm):
|
||||
|
||||
|
||||
class ConsolePortTemplateForm(ModularComponentTemplateForm):
|
||||
fieldsets = (
|
||||
FieldSet('device_type', 'module_type', 'name', 'label', 'type', 'description'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = ConsolePortTemplate
|
||||
fields = [
|
||||
@@ -1036,10 +1042,6 @@ class ConsolePortTemplateForm(ModularComponentTemplateForm):
|
||||
|
||||
|
||||
class ConsoleServerPortTemplateForm(ModularComponentTemplateForm):
|
||||
fieldsets = (
|
||||
FieldSet('device_type', 'module_type', 'name', 'label', 'type', 'description'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = ConsoleServerPortTemplate
|
||||
fields = [
|
||||
@@ -1050,7 +1052,11 @@ class ConsoleServerPortTemplateForm(ModularComponentTemplateForm):
|
||||
class PowerPortTemplateForm(ModularComponentTemplateForm):
|
||||
fieldsets = (
|
||||
FieldSet(
|
||||
'device_type', 'module_type', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description',
|
||||
TabbedGroups(
|
||||
FieldSet('device_type', name=_('Device Type')),
|
||||
FieldSet('module_type', name=_('Module Type')),
|
||||
),
|
||||
'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description',
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1072,7 +1078,13 @@ class PowerOutletTemplateForm(ModularComponentTemplateForm):
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
FieldSet('device_type', 'module_type', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description'),
|
||||
FieldSet(
|
||||
TabbedGroups(
|
||||
FieldSet('device_type', name=_('Device Type')),
|
||||
FieldSet('module_type', name=_('Module Type')),
|
||||
),
|
||||
'name', 'label', 'type', 'power_port', 'feed_leg', 'description',
|
||||
),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
@@ -1095,7 +1107,11 @@ class InterfaceTemplateForm(ModularComponentTemplateForm):
|
||||
|
||||
fieldsets = (
|
||||
FieldSet(
|
||||
'device_type', 'module_type', 'name', 'label', 'type', 'enabled', 'mgmt_only', 'description', 'bridge',
|
||||
TabbedGroups(
|
||||
FieldSet('device_type', name=_('Device Type')),
|
||||
FieldSet('module_type', name=_('Module Type')),
|
||||
),
|
||||
'name', 'label', 'type', 'enabled', 'mgmt_only', 'description', 'bridge',
|
||||
),
|
||||
FieldSet('poe_mode', 'poe_type', name=_('PoE')),
|
||||
FieldSet('rf_role', name=_('Wireless')),
|
||||
@@ -1122,8 +1138,11 @@ class FrontPortTemplateForm(ModularComponentTemplateForm):
|
||||
|
||||
fieldsets = (
|
||||
FieldSet(
|
||||
'device_type', 'module_type', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position',
|
||||
'description',
|
||||
TabbedGroups(
|
||||
FieldSet('device_type', name=_('Device Type')),
|
||||
FieldSet('module_type', name=_('Module Type')),
|
||||
),
|
||||
'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position', 'description',
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1137,7 +1156,13 @@ class FrontPortTemplateForm(ModularComponentTemplateForm):
|
||||
|
||||
class RearPortTemplateForm(ModularComponentTemplateForm):
|
||||
fieldsets = (
|
||||
FieldSet('device_type', 'module_type', 'name', 'label', 'type', 'color', 'positions', 'description'),
|
||||
FieldSet(
|
||||
TabbedGroups(
|
||||
FieldSet('device_type', name=_('Device Type')),
|
||||
FieldSet('module_type', name=_('Module Type')),
|
||||
),
|
||||
'name', 'label', 'type', 'color', 'positions', 'description',
|
||||
),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
@@ -1149,7 +1174,13 @@ class RearPortTemplateForm(ModularComponentTemplateForm):
|
||||
|
||||
class ModuleBayTemplateForm(ModularComponentTemplateForm):
|
||||
fieldsets = (
|
||||
FieldSet('device_type', 'module_type', 'name', 'label', 'position', 'description'),
|
||||
FieldSet(
|
||||
TabbedGroups(
|
||||
FieldSet('device_type', name=_('Device Type')),
|
||||
FieldSet('module_type', name=_('Module Type')),
|
||||
),
|
||||
'name', 'label', 'position', 'description',
|
||||
),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
||||
@@ -4,7 +4,7 @@ from django.utils.translation import gettext_lazy as _
|
||||
from dcim.models import *
|
||||
from netbox.forms import NetBoxModelForm
|
||||
from utilities.forms.fields import DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField
|
||||
from utilities.forms.rendering import FieldSet
|
||||
from utilities.forms.rendering import FieldSet, TabbedGroups
|
||||
from utilities.forms.widgets import APISelect
|
||||
from . import model_forms
|
||||
|
||||
@@ -118,7 +118,13 @@ class FrontPortTemplateCreateForm(ComponentCreateForm, model_forms.FrontPortTemp
|
||||
|
||||
# Override fieldsets from FrontPortTemplateForm to omit rear_port_position
|
||||
fieldsets = (
|
||||
FieldSet('device_type', 'module_type', 'name', 'label', 'type', 'color', 'rear_port', 'description'),
|
||||
FieldSet(
|
||||
TabbedGroups(
|
||||
FieldSet('device_type', name=_('Device Type')),
|
||||
FieldSet('module_type', name=_('Module Type')),
|
||||
),
|
||||
'name', 'label', 'type', 'color', 'rear_port', 'description',
|
||||
),
|
||||
)
|
||||
|
||||
class Meta(model_forms.FrontPortTemplateForm.Meta):
|
||||
|
||||
@@ -159,7 +159,7 @@ class ModuleBayTemplateImportForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = ModuleBayTemplate
|
||||
fields = [
|
||||
'device_type', 'name', 'label', 'position', 'description',
|
||||
'device_type', 'module_type', 'name', 'label', 'position', 'description',
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import itertools
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.db.models import Sum
|
||||
from django.dispatch import Signal
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
@@ -774,9 +773,28 @@ class CablePath(models.Model):
|
||||
Return a tuple containing the sum of the length of each cable in the path
|
||||
and a flag indicating whether the length is definitive.
|
||||
"""
|
||||
cable_ct = ObjectType.objects.get_for_model(Cable).pk
|
||||
|
||||
# Pre-cache cable lengths by ID
|
||||
cable_ids = self.get_cable_ids()
|
||||
cables = Cable.objects.filter(id__in=cable_ids, _abs_length__isnull=False)
|
||||
total_length = cables.aggregate(total=Sum('_abs_length'))['total']
|
||||
cables = {
|
||||
cable['pk']: cable['_abs_length']
|
||||
for cable in Cable.objects.filter(id__in=cable_ids, _abs_length__isnull=False).values('pk', '_abs_length')
|
||||
}
|
||||
|
||||
# Iterate through each set of nodes in the path. For cables, add the length of the longest cable to the total
|
||||
# length of the path.
|
||||
total_length = 0
|
||||
for node_set in self.path:
|
||||
hop_length = 0
|
||||
for node in node_set:
|
||||
ct, pk = decompile_path_node(node)
|
||||
if ct != cable_ct:
|
||||
break # Not a cable
|
||||
if pk in cables and cables[pk] > hop_length:
|
||||
hop_length = cables[pk]
|
||||
total_length += hop_length
|
||||
|
||||
is_definitive = len(cables) == len(cable_ids)
|
||||
|
||||
return total_length, is_definitive
|
||||
|
||||
@@ -732,3 +732,8 @@ class RackReservation(PrimaryModel):
|
||||
@property
|
||||
def unit_list(self):
|
||||
return array_to_string(self.units)
|
||||
|
||||
def to_objectchange(self, action):
|
||||
objectchange = super().to_objectchange(action)
|
||||
objectchange.related_object = self.rack
|
||||
return objectchange
|
||||
|
||||
@@ -2859,15 +2859,23 @@ class DeviceTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
addresses = IPAddress.objects.filter(address__family=4)
|
||||
params = {'primary_ip4_id': [addresses[0].pk, addresses[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'primary_ip4': [str(addresses[0].address), str(addresses[1].address)]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'primary_ip4_id': [addresses[2].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 0)
|
||||
params = {'primary_ip4': [str(addresses[2].address)]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 0)
|
||||
|
||||
def test_primary_ip6(self):
|
||||
addresses = IPAddress.objects.filter(address__family=6)
|
||||
params = {'primary_ip6_id': [addresses[0].pk, addresses[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'primary_ip6': [str(addresses[0].address), str(addresses[1].address)]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'primary_ip6_id': [addresses[2].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 0)
|
||||
params = {'primary_ip6': [str(addresses[2].address)]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 0)
|
||||
|
||||
def test_virtual_chassis_id(self):
|
||||
params = {'virtual_chassis_id': [VirtualChassis.objects.first().pk]}
|
||||
@@ -2961,6 +2969,29 @@ class ModuleTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
regions = (
|
||||
Region(name='Region 1', slug='region-1'),
|
||||
Region(name='Region 2', slug='region-2'),
|
||||
Region(name='Region 3', slug='region-3'),
|
||||
)
|
||||
for region in regions:
|
||||
region.save()
|
||||
|
||||
groups = (
|
||||
SiteGroup(name='Site Group 1', slug='site-group-1'),
|
||||
SiteGroup(name='Site Group 2', slug='site-group-2'),
|
||||
SiteGroup(name='Site Group 3', slug='site-group-3'),
|
||||
)
|
||||
for group in groups:
|
||||
group.save()
|
||||
|
||||
sites = Site.objects.bulk_create((
|
||||
Site(name='Site 1', slug='site-1', region=regions[0], group=groups[0]),
|
||||
Site(name='Site 2', slug='site-2', region=regions[1], group=groups[1]),
|
||||
Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]),
|
||||
Site(name='Site X', slug='site-x'),
|
||||
))
|
||||
|
||||
manufacturers = (
|
||||
Manufacturer(name='Manufacturer 1', slug='manufacturer-1'),
|
||||
Manufacturer(name='Manufacturer 2', slug='manufacturer-2'),
|
||||
@@ -2968,11 +2999,66 @@ class ModuleTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
)
|
||||
Manufacturer.objects.bulk_create(manufacturers)
|
||||
|
||||
devices = (
|
||||
create_test_device('Test Device 1'),
|
||||
create_test_device('Test Device 2'),
|
||||
create_test_device('Test Device 3'),
|
||||
device_types = (
|
||||
DeviceType(manufacturer=manufacturers[0], model='Device Type 1', slug='device-type-1'),
|
||||
DeviceType(manufacturer=manufacturers[1], model='Device Type 2', slug='device-type-2'),
|
||||
DeviceType(manufacturer=manufacturers[2], model='Device Type 3', slug='device-type-3'),
|
||||
)
|
||||
DeviceType.objects.bulk_create(device_types)
|
||||
|
||||
roles = (
|
||||
DeviceRole(name='Device Role 1', slug='device-role-1'),
|
||||
DeviceRole(name='Device Role 2', slug='device-role-2'),
|
||||
DeviceRole(name='Device Role 3', slug='device-role-3'),
|
||||
)
|
||||
for role in roles:
|
||||
role.save()
|
||||
|
||||
locations = (
|
||||
Location(name='Location 1', slug='location-1', site=sites[0]),
|
||||
Location(name='Location 2', slug='location-2', site=sites[1]),
|
||||
Location(name='Location 3', slug='location-3', site=sites[2]),
|
||||
)
|
||||
for location in locations:
|
||||
location.save()
|
||||
|
||||
racks = (
|
||||
Rack(name='Rack 1', site=sites[0]),
|
||||
Rack(name='Rack 2', site=sites[1]),
|
||||
Rack(name='Rack 3', site=sites[2]),
|
||||
)
|
||||
Rack.objects.bulk_create(racks)
|
||||
|
||||
devices = (
|
||||
Device(
|
||||
name='Test Device 1',
|
||||
device_type=device_types[0],
|
||||
role=roles[0],
|
||||
site=sites[0],
|
||||
location=locations[0],
|
||||
rack=racks[0],
|
||||
status='active',
|
||||
),
|
||||
Device(
|
||||
name='Test Device 2',
|
||||
device_type=device_types[1],
|
||||
role=roles[1],
|
||||
site=sites[1],
|
||||
location=locations[1],
|
||||
rack=racks[1],
|
||||
status='planned',
|
||||
),
|
||||
Device(
|
||||
name='Test Device 3',
|
||||
device_type=device_types[2],
|
||||
role=roles[2],
|
||||
site=sites[2],
|
||||
location=locations[2],
|
||||
rack=racks[2],
|
||||
status='offline',
|
||||
),
|
||||
)
|
||||
Device.objects.bulk_create(devices)
|
||||
|
||||
module_types = (
|
||||
ModuleType(manufacturer=manufacturers[0], model='Module Type 1'),
|
||||
@@ -3120,6 +3206,41 @@ class ModuleTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
params = {'asset_tag': ['A', 'B']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_region(self):
|
||||
regions = Region.objects.all()[:2]
|
||||
params = {'region_id': [regions[0].pk, regions[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
||||
params = {'region': [regions[0].slug, regions[1].slug]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
||||
|
||||
def test_site_group(self):
|
||||
site_groups = SiteGroup.objects.all()[:2]
|
||||
params = {'site_group_id': [site_groups[0].pk, site_groups[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
||||
params = {'site_group': [site_groups[0].slug, site_groups[1].slug]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
||||
|
||||
def test_site(self):
|
||||
sites = Site.objects.all()[:2]
|
||||
params = {'site_id': [sites[0].pk, sites[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
||||
params = {'site': [sites[0].slug, sites[1].slug]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
||||
|
||||
def test_location(self):
|
||||
locations = Location.objects.all()[:2]
|
||||
params = {'location_id': [locations[0].pk, locations[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
||||
params = {'location': [locations[0].slug, locations[1].slug]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
||||
|
||||
def test_rack(self):
|
||||
racks = Rack.objects.all()[:2]
|
||||
params = {'rack_id': [racks[0].pk, racks[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
||||
params = {'rack': [racks[0].name, racks[1].name]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
||||
|
||||
|
||||
class ConsolePortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFilterSetTests):
|
||||
queryset = ConsolePort.objects.all()
|
||||
@@ -6722,15 +6843,23 @@ class VirtualDeviceContextTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
addresses = IPAddress.objects.filter(address__family=4)
|
||||
params = {'primary_ip4_id': [addresses[0].pk, addresses[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'primary_ip4': [str(addresses[0].address), str(addresses[1].address)]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'primary_ip4_id': [addresses[2].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 0)
|
||||
params = {'primary_ip4': [str(addresses[2].address)]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 0)
|
||||
|
||||
def test_primary_ip6(self):
|
||||
addresses = IPAddress.objects.filter(address__family=6)
|
||||
params = {'primary_ip6_id': [addresses[0].pk, addresses[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'primary_ip6': [str(addresses[0].address), str(addresses[1].address)]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'primary_ip6_id': [addresses[2].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 0)
|
||||
params = {'primary_ip6': [str(addresses[2].address)]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 0)
|
||||
|
||||
|
||||
class MACAddressTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
|
||||
@@ -1217,6 +1217,13 @@ front-ports:
|
||||
- name: Front Port 3
|
||||
type: 8p8c
|
||||
rear_port: Rear Port 3
|
||||
module-bays:
|
||||
- name: Module Bay 1
|
||||
position: 1
|
||||
- name: Module Bay 2
|
||||
position: 2
|
||||
- name: Module Bay 3
|
||||
position: 3
|
||||
"""
|
||||
|
||||
# Create the manufacturer
|
||||
@@ -1234,6 +1241,7 @@ front-ports:
|
||||
'dcim.add_interfacetemplate',
|
||||
'dcim.add_frontporttemplate',
|
||||
'dcim.add_rearporttemplate',
|
||||
'dcim.add_modulebaytemplate',
|
||||
)
|
||||
|
||||
form_data = {
|
||||
@@ -1288,6 +1296,11 @@ front-ports:
|
||||
self.assertEqual(fp1.rear_port, rp1)
|
||||
self.assertEqual(fp1.rear_port_position, 1)
|
||||
|
||||
self.assertEqual(module_type.modulebaytemplates.count(), 3)
|
||||
mb1 = ModuleBayTemplate.objects.first()
|
||||
self.assertEqual(mb1.name, 'Module Bay 1')
|
||||
self.assertEqual(mb1.position, '1')
|
||||
|
||||
def test_export_objects(self):
|
||||
url = reverse('dcim:moduletype_list')
|
||||
self.add_permissions('dcim.view_moduletype')
|
||||
|
||||
@@ -22,6 +22,7 @@ from utilities.paginator import EnhancedPaginator, get_paginate_count
|
||||
from utilities.permissions import get_permission_for_model
|
||||
from utilities.query import count_related
|
||||
from utilities.query_functions import CollateAsChar
|
||||
from utilities.request import safe_for_redirect
|
||||
from utilities.views import (
|
||||
GetRelatedModelsMixin, GetReturnURLMixin, ObjectPermissionRequiredMixin, ViewTab, register_model_view
|
||||
)
|
||||
@@ -279,7 +280,7 @@ class RegionDeleteView(generic.ObjectDeleteView):
|
||||
queryset = Region.objects.all()
|
||||
|
||||
|
||||
@register_model_view(Region, 'bulk_import', detail=False)
|
||||
@register_model_view(Region, 'bulk_import', path='import', detail=False)
|
||||
class RegionBulkImportView(generic.BulkImportView):
|
||||
queryset = Region.objects.all()
|
||||
model_form = forms.RegionImportForm
|
||||
@@ -405,7 +406,7 @@ class SiteGroupDeleteView(generic.ObjectDeleteView):
|
||||
queryset = SiteGroup.objects.all()
|
||||
|
||||
|
||||
@register_model_view(SiteGroup, 'bulk_import', detail=False)
|
||||
@register_model_view(SiteGroup, 'bulk_import', path='import', detail=False)
|
||||
class SiteGroupBulkImportView(generic.BulkImportView):
|
||||
queryset = SiteGroup.objects.all()
|
||||
model_form = forms.SiteGroupImportForm
|
||||
@@ -496,7 +497,7 @@ class SiteDeleteView(generic.ObjectDeleteView):
|
||||
queryset = Site.objects.all()
|
||||
|
||||
|
||||
@register_model_view(Site, 'bulk_import', detail=False)
|
||||
@register_model_view(Site, 'bulk_import', path='import', detail=False)
|
||||
class SiteBulkImportView(generic.BulkImportView):
|
||||
queryset = Site.objects.all()
|
||||
model_form = forms.SiteImportForm
|
||||
@@ -594,7 +595,7 @@ class LocationDeleteView(generic.ObjectDeleteView):
|
||||
queryset = Location.objects.all()
|
||||
|
||||
|
||||
@register_model_view(Location, 'bulk_import', detail=False)
|
||||
@register_model_view(Location, 'bulk_import', path='import', detail=False)
|
||||
class LocationBulkImportView(generic.BulkImportView):
|
||||
queryset = Location.objects.all()
|
||||
model_form = forms.LocationImportForm
|
||||
@@ -663,7 +664,7 @@ class RackRoleDeleteView(generic.ObjectDeleteView):
|
||||
queryset = RackRole.objects.all()
|
||||
|
||||
|
||||
@register_model_view(RackRole, 'bulk_import', detail=False)
|
||||
@register_model_view(RackRole, 'bulk_import', path='import', detail=False)
|
||||
class RackRoleBulkImportView(generic.BulkImportView):
|
||||
queryset = RackRole.objects.all()
|
||||
model_form = forms.RackRoleImportForm
|
||||
@@ -724,7 +725,7 @@ class RackTypeDeleteView(generic.ObjectDeleteView):
|
||||
queryset = RackType.objects.all()
|
||||
|
||||
|
||||
@register_model_view(RackType, 'bulk_import', detail=False)
|
||||
@register_model_view(RackType, 'bulk_import', path='import', detail=False)
|
||||
class RackTypeBulkImportView(generic.BulkImportView):
|
||||
queryset = RackType.objects.all()
|
||||
model_form = forms.RackTypeImportForm
|
||||
@@ -903,7 +904,7 @@ class RackDeleteView(generic.ObjectDeleteView):
|
||||
queryset = Rack.objects.all()
|
||||
|
||||
|
||||
@register_model_view(Rack, 'bulk_import', detail=False)
|
||||
@register_model_view(Rack, 'bulk_import', path='import', detail=False)
|
||||
class RackBulkImportView(generic.BulkImportView):
|
||||
queryset = Rack.objects.all()
|
||||
model_form = forms.RackImportForm
|
||||
@@ -960,7 +961,7 @@ class RackReservationDeleteView(generic.ObjectDeleteView):
|
||||
queryset = RackReservation.objects.all()
|
||||
|
||||
|
||||
@register_model_view(RackReservation, 'bulk_import', detail=False)
|
||||
@register_model_view(RackReservation, 'bulk_import', path='import', detail=False)
|
||||
class RackReservationImportView(generic.BulkImportView):
|
||||
queryset = RackReservation.objects.all()
|
||||
model_form = forms.RackReservationImportForm
|
||||
@@ -1031,7 +1032,7 @@ class ManufacturerDeleteView(generic.ObjectDeleteView):
|
||||
queryset = Manufacturer.objects.all()
|
||||
|
||||
|
||||
@register_model_view(Manufacturer, 'bulk_import', detail=False)
|
||||
@register_model_view(Manufacturer, 'bulk_import', path='import', detail=False)
|
||||
class ManufacturerBulkImportView(generic.BulkImportView):
|
||||
queryset = Manufacturer.objects.all()
|
||||
model_form = forms.ManufacturerImportForm
|
||||
@@ -1252,7 +1253,7 @@ class DeviceTypeInventoryItemsView(DeviceTypeComponentsView):
|
||||
)
|
||||
|
||||
|
||||
@register_model_view(DeviceType, 'bulk_import', detail=False)
|
||||
@register_model_view(DeviceType, 'bulk_import', path='import', detail=False)
|
||||
class DeviceTypeImportView(generic.BulkImportView):
|
||||
additional_permissions = [
|
||||
'dcim.add_devicetype',
|
||||
@@ -1522,7 +1523,7 @@ class ModuleTypeModuleBaysView(ModuleTypeComponentsView):
|
||||
)
|
||||
|
||||
|
||||
@register_model_view(ModuleType, 'bulk_import', detail=False)
|
||||
@register_model_view(ModuleType, 'bulk_import', path='import', detail=False)
|
||||
class ModuleTypeImportView(generic.BulkImportView):
|
||||
additional_permissions = [
|
||||
'dcim.add_moduletype',
|
||||
@@ -1533,6 +1534,7 @@ class ModuleTypeImportView(generic.BulkImportView):
|
||||
'dcim.add_interfacetemplate',
|
||||
'dcim.add_frontporttemplate',
|
||||
'dcim.add_rearporttemplate',
|
||||
'dcim.add_modulebaytemplate',
|
||||
]
|
||||
queryset = ModuleType.objects.all()
|
||||
model_form = forms.ModuleTypeImportForm
|
||||
@@ -1544,6 +1546,7 @@ class ModuleTypeImportView(generic.BulkImportView):
|
||||
'interfaces': forms.InterfaceTemplateImportForm,
|
||||
'rear-ports': forms.RearPortTemplateImportForm,
|
||||
'front-ports': forms.FrontPortTemplateImportForm,
|
||||
'module-bays': forms.ModuleBayTemplateImportForm,
|
||||
}
|
||||
|
||||
def prep_related_object_data(self, parent, data):
|
||||
@@ -2018,7 +2021,7 @@ class DeviceRoleDeleteView(generic.ObjectDeleteView):
|
||||
queryset = DeviceRole.objects.all()
|
||||
|
||||
|
||||
@register_model_view(DeviceRole, 'bulk_import', detail=False)
|
||||
@register_model_view(DeviceRole, 'bulk_import', path='import', detail=False)
|
||||
class DeviceRoleBulkImportView(generic.BulkImportView):
|
||||
queryset = DeviceRole.objects.all()
|
||||
model_form = forms.DeviceRoleImportForm
|
||||
@@ -2082,7 +2085,7 @@ class PlatformDeleteView(generic.ObjectDeleteView):
|
||||
queryset = Platform.objects.all()
|
||||
|
||||
|
||||
@register_model_view(Platform, 'bulk_import', detail=False)
|
||||
@register_model_view(Platform, 'bulk_import', path='import', detail=False)
|
||||
class PlatformBulkImportView(generic.BulkImportView):
|
||||
queryset = Platform.objects.all()
|
||||
model_form = forms.PlatformImportForm
|
||||
@@ -2365,7 +2368,7 @@ class DeviceVirtualMachinesView(generic.ObjectChildrenView):
|
||||
return self.child_model.objects.restrict(request.user, 'view').filter(cluster=parent.cluster, device=parent)
|
||||
|
||||
|
||||
@register_model_view(Device, 'bulk_import', detail=False)
|
||||
@register_model_view(Device, 'bulk_import', path='import', detail=False)
|
||||
class DeviceBulkImportView(generic.BulkImportView):
|
||||
queryset = Device.objects.all()
|
||||
model_form = forms.DeviceImportForm
|
||||
@@ -2438,7 +2441,7 @@ class ModuleDeleteView(generic.ObjectDeleteView):
|
||||
queryset = Module.objects.all()
|
||||
|
||||
|
||||
@register_model_view(Module, 'bulk_import', detail=False)
|
||||
@register_model_view(Module, 'bulk_import', path='import', detail=False)
|
||||
class ModuleBulkImportView(generic.BulkImportView):
|
||||
queryset = Module.objects.all()
|
||||
model_form = forms.ModuleImportForm
|
||||
@@ -2499,7 +2502,7 @@ class ConsolePortDeleteView(generic.ObjectDeleteView):
|
||||
queryset = ConsolePort.objects.all()
|
||||
|
||||
|
||||
@register_model_view(ConsolePort, 'bulk_import', detail=False)
|
||||
@register_model_view(ConsolePort, 'bulk_import', path='import', detail=False)
|
||||
class ConsolePortBulkImportView(generic.BulkImportView):
|
||||
queryset = ConsolePort.objects.all()
|
||||
model_form = forms.ConsolePortImportForm
|
||||
@@ -2574,7 +2577,7 @@ class ConsoleServerPortDeleteView(generic.ObjectDeleteView):
|
||||
queryset = ConsoleServerPort.objects.all()
|
||||
|
||||
|
||||
@register_model_view(ConsoleServerPort, 'bulk_import', detail=False)
|
||||
@register_model_view(ConsoleServerPort, 'bulk_import', path='import', detail=False)
|
||||
class ConsoleServerPortBulkImportView(generic.BulkImportView):
|
||||
queryset = ConsoleServerPort.objects.all()
|
||||
model_form = forms.ConsoleServerPortImportForm
|
||||
@@ -2649,7 +2652,7 @@ class PowerPortDeleteView(generic.ObjectDeleteView):
|
||||
queryset = PowerPort.objects.all()
|
||||
|
||||
|
||||
@register_model_view(PowerPort, 'bulk_import', detail=False)
|
||||
@register_model_view(PowerPort, 'bulk_import', path='import', detail=False)
|
||||
class PowerPortBulkImportView(generic.BulkImportView):
|
||||
queryset = PowerPort.objects.all()
|
||||
model_form = forms.PowerPortImportForm
|
||||
@@ -2724,7 +2727,7 @@ class PowerOutletDeleteView(generic.ObjectDeleteView):
|
||||
queryset = PowerOutlet.objects.all()
|
||||
|
||||
|
||||
@register_model_view(PowerOutlet, 'bulk_import', detail=False)
|
||||
@register_model_view(PowerOutlet, 'bulk_import', path='import', detail=False)
|
||||
class PowerOutletBulkImportView(generic.BulkImportView):
|
||||
queryset = PowerOutlet.objects.all()
|
||||
model_form = forms.PowerOutletImportForm
|
||||
@@ -2856,7 +2859,7 @@ class InterfaceDeleteView(generic.ObjectDeleteView):
|
||||
queryset = Interface.objects.all()
|
||||
|
||||
|
||||
@register_model_view(Interface, 'bulk_import', detail=False)
|
||||
@register_model_view(Interface, 'bulk_import', path='import', detail=False)
|
||||
class InterfaceBulkImportView(generic.BulkImportView):
|
||||
queryset = Interface.objects.all()
|
||||
model_form = forms.InterfaceImportForm
|
||||
@@ -2942,7 +2945,7 @@ class FrontPortDeleteView(generic.ObjectDeleteView):
|
||||
queryset = FrontPort.objects.all()
|
||||
|
||||
|
||||
@register_model_view(FrontPort, 'bulk_import', detail=False)
|
||||
@register_model_view(FrontPort, 'bulk_import', path='import', detail=False)
|
||||
class FrontPortBulkImportView(generic.BulkImportView):
|
||||
queryset = FrontPort.objects.all()
|
||||
model_form = forms.FrontPortImportForm
|
||||
@@ -3017,7 +3020,7 @@ class RearPortDeleteView(generic.ObjectDeleteView):
|
||||
queryset = RearPort.objects.all()
|
||||
|
||||
|
||||
@register_model_view(RearPort, 'bulk_import', detail=False)
|
||||
@register_model_view(RearPort, 'bulk_import', path='import', detail=False)
|
||||
class RearPortBulkImportView(generic.BulkImportView):
|
||||
queryset = RearPort.objects.all()
|
||||
model_form = forms.RearPortImportForm
|
||||
@@ -3092,7 +3095,7 @@ class ModuleBayDeleteView(generic.ObjectDeleteView):
|
||||
queryset = ModuleBay.objects.all()
|
||||
|
||||
|
||||
@register_model_view(ModuleBay, 'bulk_import', detail=False)
|
||||
@register_model_view(ModuleBay, 'bulk_import', path='import', detail=False)
|
||||
class ModuleBayBulkImportView(generic.BulkImportView):
|
||||
queryset = ModuleBay.objects.all()
|
||||
model_form = forms.ModuleBayImportForm
|
||||
@@ -3239,7 +3242,7 @@ class DeviceBayDepopulateView(generic.ObjectEditView):
|
||||
})
|
||||
|
||||
|
||||
@register_model_view(DeviceBay, 'bulk_import', detail=False)
|
||||
@register_model_view(DeviceBay, 'bulk_import', path='import', detail=False)
|
||||
class DeviceBayBulkImportView(generic.BulkImportView):
|
||||
queryset = DeviceBay.objects.all()
|
||||
model_form = forms.DeviceBayImportForm
|
||||
@@ -3305,7 +3308,7 @@ class InventoryItemDeleteView(generic.ObjectDeleteView):
|
||||
queryset = InventoryItem.objects.all()
|
||||
|
||||
|
||||
@register_model_view(InventoryItem, 'bulk_import', detail=False)
|
||||
@register_model_view(InventoryItem, 'bulk_import', path='import', detail=False)
|
||||
class InventoryItemBulkImportView(generic.BulkImportView):
|
||||
queryset = InventoryItem.objects.all()
|
||||
model_form = forms.InventoryItemImportForm
|
||||
@@ -3386,7 +3389,7 @@ class InventoryItemRoleDeleteView(generic.ObjectDeleteView):
|
||||
queryset = InventoryItemRole.objects.all()
|
||||
|
||||
|
||||
@register_model_view(InventoryItemRole, 'bulk_import', detail=False)
|
||||
@register_model_view(InventoryItemRole, 'bulk_import', path='import', detail=False)
|
||||
class InventoryItemRoleBulkImportView(generic.BulkImportView):
|
||||
queryset = InventoryItemRole.objects.all()
|
||||
model_form = forms.InventoryItemRoleImportForm
|
||||
@@ -3582,7 +3585,7 @@ class CableDeleteView(generic.ObjectDeleteView):
|
||||
queryset = Cable.objects.all()
|
||||
|
||||
|
||||
@register_model_view(Cable, 'bulk_import', detail=False)
|
||||
@register_model_view(Cable, 'bulk_import', path='import', detail=False)
|
||||
class CableBulkImportView(generic.BulkImportView):
|
||||
queryset = Cable.objects.all()
|
||||
model_form = forms.CableImportForm
|
||||
@@ -3811,7 +3814,7 @@ class VirtualChassisAddMemberView(ObjectPermissionRequiredMixin, GetReturnURLMix
|
||||
)
|
||||
))
|
||||
|
||||
if '_addanother' in request.POST:
|
||||
if '_addanother' in request.POST and safe_for_redirect(request.get_full_path()):
|
||||
return redirect(request.get_full_path())
|
||||
|
||||
return redirect(self.get_return_url(request, device))
|
||||
@@ -3883,7 +3886,7 @@ class VirtualChassisRemoveMemberView(ObjectPermissionRequiredMixin, GetReturnURL
|
||||
})
|
||||
|
||||
|
||||
@register_model_view(VirtualChassis, 'bulk_import', detail=False)
|
||||
@register_model_view(VirtualChassis, 'bulk_import', path='import', detail=False)
|
||||
class VirtualChassisBulkImportView(generic.BulkImportView):
|
||||
queryset = VirtualChassis.objects.all()
|
||||
model_form = forms.VirtualChassisImportForm
|
||||
@@ -3940,7 +3943,7 @@ class PowerPanelDeleteView(generic.ObjectDeleteView):
|
||||
queryset = PowerPanel.objects.all()
|
||||
|
||||
|
||||
@register_model_view(PowerPanel, 'bulk_import', detail=False)
|
||||
@register_model_view(PowerPanel, 'bulk_import', path='import', detail=False)
|
||||
class PowerPanelBulkImportView(generic.BulkImportView):
|
||||
queryset = PowerPanel.objects.all()
|
||||
model_form = forms.PowerPanelImportForm
|
||||
@@ -3992,7 +3995,7 @@ class PowerFeedDeleteView(generic.ObjectDeleteView):
|
||||
queryset = PowerFeed.objects.all()
|
||||
|
||||
|
||||
@register_model_view(PowerFeed, 'bulk_import', detail=False)
|
||||
@register_model_view(PowerFeed, 'bulk_import', path='import', detail=False)
|
||||
class PowerFeedBulkImportView(generic.BulkImportView):
|
||||
queryset = PowerFeed.objects.all()
|
||||
model_form = forms.PowerFeedImportForm
|
||||
@@ -4064,7 +4067,7 @@ class VirtualDeviceContextDeleteView(generic.ObjectDeleteView):
|
||||
queryset = VirtualDeviceContext.objects.all()
|
||||
|
||||
|
||||
@register_model_view(VirtualDeviceContext, 'bulk_import', detail=False)
|
||||
@register_model_view(VirtualDeviceContext, 'bulk_import', path='import', detail=False)
|
||||
class VirtualDeviceContextBulkImportView(generic.BulkImportView):
|
||||
queryset = VirtualDeviceContext.objects.all()
|
||||
model_form = forms.VirtualDeviceContextImportForm
|
||||
@@ -4114,7 +4117,7 @@ class MACAddressDeleteView(generic.ObjectDeleteView):
|
||||
queryset = MACAddress.objects.all()
|
||||
|
||||
|
||||
@register_model_view(MACAddress, 'bulk_import', detail=False)
|
||||
@register_model_view(MACAddress, 'bulk_import', path='import', detail=False)
|
||||
class MACAddressBulkImportView(generic.BulkImportView):
|
||||
queryset = MACAddress.objects.all()
|
||||
model_form = forms.MACAddressImportForm
|
||||
|
||||
@@ -6,7 +6,7 @@ from django.db import migrations, models
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('core', '0014_remove_redundant_indexes'),
|
||||
('core', '0015_remove_redundant_indexes'),
|
||||
('extras', '0127_configtemplate_as_attachment_and_more'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
@@ -123,7 +123,7 @@ class ScriptModule(PythonModuleMixin, JobsMixin, ManagedFile):
|
||||
ordered = [
|
||||
script_objects.pop(sc) for sc in self.module_scripts.keys() if sc in script_objects
|
||||
]
|
||||
ordered.extend(script_objects.items())
|
||||
ordered.extend(script_objects.values())
|
||||
return ordered
|
||||
|
||||
@property
|
||||
|
||||
@@ -5,6 +5,8 @@ from django.utils.html import format_html
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from extras.models import *
|
||||
from core.tables import JobTable
|
||||
from core.models import Job
|
||||
from netbox.constants import EMPTY_TABLE_TEXT
|
||||
from netbox.events import get_event_text
|
||||
from netbox.tables import BaseTable, NetBoxTable, columns
|
||||
@@ -26,6 +28,7 @@ __all__ = (
|
||||
'SavedFilterTable',
|
||||
'ReportResultsTable',
|
||||
'ScriptResultsTable',
|
||||
'ScriptJobTable',
|
||||
'SubscriptionTable',
|
||||
'TableConfigTable',
|
||||
'TaggedItemTable',
|
||||
@@ -693,6 +696,23 @@ class ScriptResultsTable(BaseTable):
|
||||
return format_html("<a href='{}'>{}</a>", value, value)
|
||||
|
||||
|
||||
class ScriptJobTable(JobTable):
|
||||
id = tables.TemplateColumn(
|
||||
template_code="""<a href="{% url 'extras:script_result' job_pk=record.pk %}">{{ record.id }}</a>""",
|
||||
verbose_name=_('ID'),
|
||||
)
|
||||
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = Job
|
||||
fields = (
|
||||
'pk', 'id', 'object_type', 'object', 'name', 'status', 'created', 'scheduled', 'interval', 'started',
|
||||
'completed', 'user', 'error', 'job_id',
|
||||
)
|
||||
default_columns = (
|
||||
'pk', 'id', 'object_type', 'object', 'name', 'status', 'created', 'started', 'completed', 'user',
|
||||
)
|
||||
|
||||
|
||||
class ReportResultsTable(BaseTable):
|
||||
index = tables.Column(
|
||||
verbose_name=_('Line')
|
||||
|
||||
@@ -14,7 +14,6 @@ from jinja2.exceptions import TemplateError
|
||||
|
||||
from core.choices import ManagedFileRootPathChoices
|
||||
from core.models import Job
|
||||
from core.tables import JobTable
|
||||
from dcim.models import Device, DeviceRole, Platform
|
||||
from extras.choices import LogLevelChoices
|
||||
from extras.dashboard.forms import DashboardWidgetAddForm, DashboardWidgetForm
|
||||
@@ -36,7 +35,7 @@ from virtualization.models import VirtualMachine
|
||||
from . import filtersets, forms, tables
|
||||
from .constants import LOG_LEVEL_RANK
|
||||
from .models import *
|
||||
from .tables import ReportResultsTable, ScriptResultsTable
|
||||
from .tables import ReportResultsTable, ScriptResultsTable, ScriptJobTable
|
||||
|
||||
|
||||
#
|
||||
@@ -83,7 +82,7 @@ class CustomFieldDeleteView(generic.ObjectDeleteView):
|
||||
queryset = CustomField.objects.select_related('choice_set')
|
||||
|
||||
|
||||
@register_model_view(CustomField, 'bulk_import', detail=False)
|
||||
@register_model_view(CustomField, 'bulk_import', path='import', detail=False)
|
||||
class CustomFieldBulkImportView(generic.BulkImportView):
|
||||
queryset = CustomField.objects.select_related('choice_set')
|
||||
model_form = forms.CustomFieldImportForm
|
||||
@@ -152,7 +151,7 @@ class CustomFieldChoiceSetDeleteView(generic.ObjectDeleteView):
|
||||
queryset = CustomFieldChoiceSet.objects.all()
|
||||
|
||||
|
||||
@register_model_view(CustomFieldChoiceSet, 'bulk_import', detail=False)
|
||||
@register_model_view(CustomFieldChoiceSet, 'bulk_import', path='import', detail=False)
|
||||
class CustomFieldChoiceSetBulkImportView(generic.BulkImportView):
|
||||
queryset = CustomFieldChoiceSet.objects.all()
|
||||
model_form = forms.CustomFieldChoiceSetImportForm
|
||||
@@ -202,7 +201,7 @@ class CustomLinkDeleteView(generic.ObjectDeleteView):
|
||||
queryset = CustomLink.objects.all()
|
||||
|
||||
|
||||
@register_model_view(CustomLink, 'bulk_import', detail=False)
|
||||
@register_model_view(CustomLink, 'bulk_import', path='import', detail=False)
|
||||
class CustomLinkBulkImportView(generic.BulkImportView):
|
||||
queryset = CustomLink.objects.all()
|
||||
model_form = forms.CustomLinkImportForm
|
||||
@@ -257,7 +256,7 @@ class ExportTemplateDeleteView(generic.ObjectDeleteView):
|
||||
queryset = ExportTemplate.objects.all()
|
||||
|
||||
|
||||
@register_model_view(ExportTemplate, 'bulk_import', detail=False)
|
||||
@register_model_view(ExportTemplate, 'bulk_import', path='import', detail=False)
|
||||
class ExportTemplateBulkImportView(generic.BulkImportView):
|
||||
queryset = ExportTemplate.objects.all()
|
||||
model_form = forms.ExportTemplateImportForm
|
||||
@@ -317,7 +316,7 @@ class SavedFilterDeleteView(SharedObjectViewMixin, generic.ObjectDeleteView):
|
||||
queryset = SavedFilter.objects.all()
|
||||
|
||||
|
||||
@register_model_view(SavedFilter, 'bulk_import', detail=False)
|
||||
@register_model_view(SavedFilter, 'bulk_import', path='import', detail=False)
|
||||
class SavedFilterBulkImportView(SharedObjectViewMixin, generic.BulkImportView):
|
||||
queryset = SavedFilter.objects.all()
|
||||
model_form = forms.SavedFilterImportForm
|
||||
@@ -457,7 +456,7 @@ class NotificationGroupDeleteView(generic.ObjectDeleteView):
|
||||
queryset = NotificationGroup.objects.all()
|
||||
|
||||
|
||||
@register_model_view(NotificationGroup, 'bulk_import', detail=False)
|
||||
@register_model_view(NotificationGroup, 'bulk_import', path='import', detail=False)
|
||||
class NotificationGroupBulkImportView(generic.BulkImportView):
|
||||
queryset = NotificationGroup.objects.all()
|
||||
model_form = forms.NotificationGroupImportForm
|
||||
@@ -603,7 +602,7 @@ class WebhookDeleteView(generic.ObjectDeleteView):
|
||||
queryset = Webhook.objects.all()
|
||||
|
||||
|
||||
@register_model_view(Webhook, 'bulk_import', detail=False)
|
||||
@register_model_view(Webhook, 'bulk_import', path='import', detail=False)
|
||||
class WebhookBulkImportView(generic.BulkImportView):
|
||||
queryset = Webhook.objects.all()
|
||||
model_form = forms.WebhookImportForm
|
||||
@@ -653,7 +652,7 @@ class EventRuleDeleteView(generic.ObjectDeleteView):
|
||||
queryset = EventRule.objects.all()
|
||||
|
||||
|
||||
@register_model_view(EventRule, 'bulk_import', detail=False)
|
||||
@register_model_view(EventRule, 'bulk_import', path='import', detail=False)
|
||||
class EventRuleBulkImportView(generic.BulkImportView):
|
||||
queryset = EventRule.objects.all()
|
||||
model_form = forms.EventRuleImportForm
|
||||
@@ -726,7 +725,7 @@ class TagDeleteView(generic.ObjectDeleteView):
|
||||
queryset = Tag.objects.all()
|
||||
|
||||
|
||||
@register_model_view(Tag, 'bulk_import', detail=False)
|
||||
@register_model_view(Tag, 'bulk_import', path='import', detail=False)
|
||||
class TagBulkImportView(generic.BulkImportView):
|
||||
queryset = Tag.objects.all()
|
||||
model_form = forms.TagImportForm
|
||||
@@ -902,7 +901,7 @@ class ConfigTemplateDeleteView(generic.ObjectDeleteView):
|
||||
queryset = ConfigTemplate.objects.all()
|
||||
|
||||
|
||||
@register_model_view(ConfigTemplate, 'bulk_import', detail=False)
|
||||
@register_model_view(ConfigTemplate, 'bulk_import', path='import', detail=False)
|
||||
class ConfigTemplateBulkImportView(generic.BulkImportView):
|
||||
queryset = ConfigTemplate.objects.all()
|
||||
model_form = forms.ConfigTemplateImportForm
|
||||
@@ -1081,7 +1080,7 @@ class JournalEntryDeleteView(generic.ObjectDeleteView):
|
||||
return reverse(viewname, kwargs={'pk': obj.pk})
|
||||
|
||||
|
||||
@register_model_view(JournalEntry, 'bulk_import', detail=False)
|
||||
@register_model_view(JournalEntry, 'bulk_import', path='import', detail=False)
|
||||
class JournalEntryBulkImportView(generic.BulkImportView):
|
||||
queryset = JournalEntry.objects.all()
|
||||
model_form = forms.JournalEntryImportForm
|
||||
@@ -1393,7 +1392,7 @@ class ScriptJobsView(BaseScriptView):
|
||||
def get(self, request, **kwargs):
|
||||
script = self.get_object(**kwargs)
|
||||
|
||||
jobs_table = JobTable(
|
||||
jobs_table = ScriptJobTable(
|
||||
data=script.jobs.all(),
|
||||
orderable=False,
|
||||
user=request.user
|
||||
|
||||
@@ -1256,8 +1256,20 @@ class PrimaryIPFilterSet(django_filters.FilterSet):
|
||||
queryset=IPAddress.objects.all(),
|
||||
label=_('Primary IPv4 (ID)'),
|
||||
)
|
||||
primary_ip4 = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='primary_ip4__address',
|
||||
queryset=IPAddress.objects.all(),
|
||||
to_field_name='address',
|
||||
label=_('Primary IPv4 (address)'),
|
||||
)
|
||||
primary_ip6_id = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='primary_ip6',
|
||||
queryset=IPAddress.objects.all(),
|
||||
label=_('Primary IPv6 (ID)'),
|
||||
)
|
||||
primary_ip6 = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='primary_ip6__address',
|
||||
queryset=IPAddress.objects.all(),
|
||||
to_field_name='address',
|
||||
label=_('Primary IPv6 (address)'),
|
||||
)
|
||||
|
||||
@@ -864,6 +864,7 @@ class ServiceCreateForm(ServiceForm):
|
||||
# Fields which may be populated from a ServiceTemplate are not required
|
||||
for field in ('name', 'protocol', 'ports'):
|
||||
self.fields[field].required = False
|
||||
self.fields[field].widget.is_required = False
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.contrib.postgres.fields import ArrayField, IntegerRangeField
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||
@@ -6,7 +7,7 @@ from django.db import models
|
||||
from django.db.backends.postgresql.psycopg_any import NumericRange
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from dcim.models import Interface
|
||||
from dcim.models import Interface, Site, SiteGroup
|
||||
from ipam.choices import *
|
||||
from ipam.constants import *
|
||||
from ipam.querysets import VLANQuerySet, VLANGroupQuerySet
|
||||
@@ -285,12 +286,20 @@ class VLAN(PrimaryModel):
|
||||
super().clean()
|
||||
|
||||
# Validate VLAN group (if assigned)
|
||||
if self.group and self.site and self.group.scope != self.site:
|
||||
raise ValidationError(
|
||||
_(
|
||||
"VLAN is assigned to group {group} (scope: {scope}); cannot also assign to site {site}."
|
||||
).format(group=self.group, scope=self.group.scope, site=self.site)
|
||||
)
|
||||
if self.group and self.site and self.group.scope_type == ContentType.objects.get_for_model(Site):
|
||||
if self.site != self.group.scope:
|
||||
raise ValidationError(
|
||||
_(
|
||||
"VLAN is assigned to group {group} (scope: {scope}); cannot also assign to site {site}."
|
||||
).format(group=self.group, scope=self.group.scope, site=self.site)
|
||||
)
|
||||
if self.group and self.site and self.group.scope_type == ContentType.objects.get_for_model(SiteGroup):
|
||||
if self.site not in self.group.scope.sites.all():
|
||||
raise ValidationError(
|
||||
_(
|
||||
"The assigned site {site} is not a member of the assigned group {group} (scope: {scope})."
|
||||
).format(group=self.group, scope=self.group.scope, site=self.site)
|
||||
)
|
||||
|
||||
# Check that the VLAN ID is permitted in the assigned group (if any)
|
||||
if self.group:
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.test import TestCase, override_settings
|
||||
from netaddr import IPNetwork, IPSet
|
||||
from utilities.data import string_to_ranges
|
||||
|
||||
from dcim.models import Site, SiteGroup
|
||||
from ipam.choices import *
|
||||
from ipam.models import *
|
||||
|
||||
@@ -679,3 +681,54 @@ class TestVLAN(TestCase):
|
||||
)
|
||||
with self.assertRaises(ValidationError):
|
||||
vlan.full_clean()
|
||||
|
||||
def test_vlan_group_site_validation(self):
|
||||
sitegroup = SiteGroup.objects.create(
|
||||
name='Site Group 1',
|
||||
slug='site-group-1',
|
||||
)
|
||||
sites = Site.objects.bulk_create((
|
||||
Site(
|
||||
name='Site 1',
|
||||
slug='site-1',
|
||||
),
|
||||
Site(
|
||||
name='Site 2',
|
||||
slug='site-2',
|
||||
),
|
||||
))
|
||||
sitegroup.sites.add(sites[0])
|
||||
vlangroups = VLANGroup.objects.bulk_create((
|
||||
VLANGroup(
|
||||
name='VLAN Group 1',
|
||||
slug='vlan-group-1',
|
||||
scope=sitegroup,
|
||||
scope_type=ContentType.objects.get_for_model(SiteGroup),
|
||||
),
|
||||
VLANGroup(
|
||||
name='VLAN Group 2',
|
||||
slug='vlan-group-2',
|
||||
scope=sites[0],
|
||||
scope_type=ContentType.objects.get_for_model(Site),
|
||||
),
|
||||
VLANGroup(
|
||||
name='VLAN Group 2',
|
||||
slug='vlan-group-2',
|
||||
scope=sites[1],
|
||||
scope_type=ContentType.objects.get_for_model(Site),
|
||||
),
|
||||
))
|
||||
vlan = VLAN(
|
||||
name='VLAN 1',
|
||||
vid=1,
|
||||
group=vlangroups[0],
|
||||
site=sites[0],
|
||||
)
|
||||
|
||||
# VLAN Group 1 and 2 should be valid
|
||||
vlan.full_clean()
|
||||
vlan.group = vlangroups[1]
|
||||
vlan.full_clean()
|
||||
vlan.group = vlangroups[2]
|
||||
with self.assertRaises(ValidationError):
|
||||
vlan.full_clean()
|
||||
|
||||
@@ -69,7 +69,7 @@ class VRFDeleteView(generic.ObjectDeleteView):
|
||||
queryset = VRF.objects.all()
|
||||
|
||||
|
||||
@register_model_view(VRF, 'bulk_import', detail=False)
|
||||
@register_model_view(VRF, 'bulk_import', path='import', detail=False)
|
||||
class VRFBulkImportView(generic.BulkImportView):
|
||||
queryset = VRF.objects.all()
|
||||
model_form = forms.VRFImportForm
|
||||
@@ -119,7 +119,7 @@ class RouteTargetDeleteView(generic.ObjectDeleteView):
|
||||
queryset = RouteTarget.objects.all()
|
||||
|
||||
|
||||
@register_model_view(RouteTarget, 'bulk_import', detail=False)
|
||||
@register_model_view(RouteTarget, 'bulk_import', path='import', detail=False)
|
||||
class RouteTargetBulkImportView(generic.BulkImportView):
|
||||
queryset = RouteTarget.objects.all()
|
||||
model_form = forms.RouteTargetImportForm
|
||||
@@ -176,7 +176,7 @@ class RIRDeleteView(generic.ObjectDeleteView):
|
||||
queryset = RIR.objects.all()
|
||||
|
||||
|
||||
@register_model_view(RIR, 'bulk_import', detail=False)
|
||||
@register_model_view(RIR, 'bulk_import', path='import', detail=False)
|
||||
class RIRBulkImportView(generic.BulkImportView):
|
||||
queryset = RIR.objects.all()
|
||||
model_form = forms.RIRImportForm
|
||||
@@ -251,7 +251,7 @@ class ASNRangeDeleteView(generic.ObjectDeleteView):
|
||||
queryset = ASNRange.objects.all()
|
||||
|
||||
|
||||
@register_model_view(ASNRange, 'bulk_import', detail=False)
|
||||
@register_model_view(ASNRange, 'bulk_import', path='import', detail=False)
|
||||
class ASNRangeBulkImportView(generic.BulkImportView):
|
||||
queryset = ASNRange.objects.all()
|
||||
model_form = forms.ASNRangeImportForm
|
||||
@@ -316,7 +316,7 @@ class ASNDeleteView(generic.ObjectDeleteView):
|
||||
queryset = ASN.objects.all()
|
||||
|
||||
|
||||
@register_model_view(ASN, 'bulk_import', detail=False)
|
||||
@register_model_view(ASN, 'bulk_import', path='import', detail=False)
|
||||
class ASNBulkImportView(generic.BulkImportView):
|
||||
queryset = ASN.objects.all()
|
||||
model_form = forms.ASNImportForm
|
||||
@@ -408,7 +408,7 @@ class AggregateDeleteView(generic.ObjectDeleteView):
|
||||
queryset = Aggregate.objects.all()
|
||||
|
||||
|
||||
@register_model_view(Aggregate, 'bulk_import', detail=False)
|
||||
@register_model_view(Aggregate, 'bulk_import', path='import', detail=False)
|
||||
class AggregateBulkImportView(generic.BulkImportView):
|
||||
queryset = Aggregate.objects.all()
|
||||
model_form = forms.AggregateImportForm
|
||||
@@ -471,7 +471,7 @@ class RoleDeleteView(generic.ObjectDeleteView):
|
||||
queryset = Role.objects.all()
|
||||
|
||||
|
||||
@register_model_view(Role, 'bulk_import', detail=False)
|
||||
@register_model_view(Role, 'bulk_import', path='import', detail=False)
|
||||
class RoleBulkImportView(generic.BulkImportView):
|
||||
queryset = Role.objects.all()
|
||||
model_form = forms.RoleImportForm
|
||||
@@ -657,7 +657,7 @@ class PrefixDeleteView(generic.ObjectDeleteView):
|
||||
queryset = Prefix.objects.all()
|
||||
|
||||
|
||||
@register_model_view(Prefix, 'bulk_import', detail=False)
|
||||
@register_model_view(Prefix, 'bulk_import', path='import', detail=False)
|
||||
class PrefixBulkImportView(generic.BulkImportView):
|
||||
queryset = Prefix.objects.all()
|
||||
model_form = forms.PrefixImportForm
|
||||
@@ -746,7 +746,7 @@ class IPRangeDeleteView(generic.ObjectDeleteView):
|
||||
queryset = IPRange.objects.all()
|
||||
|
||||
|
||||
@register_model_view(IPRange, 'bulk_import', detail=False)
|
||||
@register_model_view(IPRange, 'bulk_import', path='import', detail=False)
|
||||
class IPRangeBulkImportView(generic.BulkImportView):
|
||||
queryset = IPRange.objects.all()
|
||||
model_form = forms.IPRangeImportForm
|
||||
@@ -910,7 +910,7 @@ class IPAddressBulkCreateView(generic.BulkCreateView):
|
||||
template_name = 'ipam/ipaddress_bulk_add.html'
|
||||
|
||||
|
||||
@register_model_view(IPAddress, 'bulk_import', detail=False)
|
||||
@register_model_view(IPAddress, 'bulk_import', path='import', detail=False)
|
||||
class IPAddressBulkImportView(generic.BulkImportView):
|
||||
queryset = IPAddress.objects.all()
|
||||
model_form = forms.IPAddressImportForm
|
||||
@@ -983,7 +983,7 @@ class VLANGroupDeleteView(generic.ObjectDeleteView):
|
||||
queryset = VLANGroup.objects.all()
|
||||
|
||||
|
||||
@register_model_view(VLANGroup, 'bulk_import', detail=False)
|
||||
@register_model_view(VLANGroup, 'bulk_import', path='import', detail=False)
|
||||
class VLANGroupBulkImportView(generic.BulkImportView):
|
||||
queryset = VLANGroup.objects.all()
|
||||
model_form = forms.VLANGroupImportForm
|
||||
@@ -1070,7 +1070,7 @@ class VLANTranslationPolicyDeleteView(generic.ObjectDeleteView):
|
||||
queryset = VLANTranslationPolicy.objects.all()
|
||||
|
||||
|
||||
@register_model_view(VLANTranslationPolicy, 'bulk_import', detail=False)
|
||||
@register_model_view(VLANTranslationPolicy, 'bulk_import', path='import', detail=False)
|
||||
class VLANTranslationPolicyBulkImportView(generic.BulkImportView):
|
||||
queryset = VLANTranslationPolicy.objects.all()
|
||||
model_form = forms.VLANTranslationPolicyImportForm
|
||||
@@ -1125,7 +1125,7 @@ class VLANTranslationRuleDeleteView(generic.ObjectDeleteView):
|
||||
queryset = VLANTranslationRule.objects.all()
|
||||
|
||||
|
||||
@register_model_view(VLANTranslationRule, 'bulk_import', detail=False)
|
||||
@register_model_view(VLANTranslationRule, 'bulk_import', path='import', detail=False)
|
||||
class VLANTranslationRuleBulkImportView(generic.BulkImportView):
|
||||
queryset = VLANTranslationRule.objects.all()
|
||||
model_form = forms.VLANTranslationRuleImportForm
|
||||
@@ -1218,7 +1218,7 @@ class FHRPGroupDeleteView(generic.ObjectDeleteView):
|
||||
queryset = FHRPGroup.objects.all()
|
||||
|
||||
|
||||
@register_model_view(FHRPGroup, 'bulk_import', detail=False)
|
||||
@register_model_view(FHRPGroup, 'bulk_import', path='import', detail=False)
|
||||
class FHRPGroupBulkImportView(generic.BulkImportView):
|
||||
queryset = FHRPGroup.objects.all()
|
||||
model_form = forms.FHRPGroupImportForm
|
||||
@@ -1344,7 +1344,7 @@ class VLANDeleteView(generic.ObjectDeleteView):
|
||||
queryset = VLAN.objects.all()
|
||||
|
||||
|
||||
@register_model_view(VLAN, 'bulk_import', detail=False)
|
||||
@register_model_view(VLAN, 'bulk_import', path='import', detail=False)
|
||||
class VLANBulkImportView(generic.BulkImportView):
|
||||
queryset = VLAN.objects.all()
|
||||
model_form = forms.VLANImportForm
|
||||
@@ -1394,7 +1394,7 @@ class ServiceTemplateDeleteView(generic.ObjectDeleteView):
|
||||
queryset = ServiceTemplate.objects.all()
|
||||
|
||||
|
||||
@register_model_view(ServiceTemplate, 'bulk_import', detail=False)
|
||||
@register_model_view(ServiceTemplate, 'bulk_import', path='import', detail=False)
|
||||
class ServiceTemplateBulkImportView(generic.BulkImportView):
|
||||
queryset = ServiceTemplate.objects.all()
|
||||
model_form = forms.ServiceTemplateImportForm
|
||||
@@ -1461,7 +1461,7 @@ class ServiceDeleteView(generic.ObjectDeleteView):
|
||||
queryset = Service.objects.all()
|
||||
|
||||
|
||||
@register_model_view(Service, 'bulk_import', detail=False)
|
||||
@register_model_view(Service, 'bulk_import', path='import', detail=False)
|
||||
class ServiceBulkImportView(generic.BulkImportView):
|
||||
queryset = Service.objects.all()
|
||||
model_form = forms.ServiceImportForm
|
||||
|
||||
@@ -442,7 +442,7 @@ INSTALLED_APPS = [
|
||||
'drf_spectacular',
|
||||
'drf_spectacular_sidecar',
|
||||
]
|
||||
if not DEBUG:
|
||||
if not DEBUG and 'collectstatic' not in sys.argv:
|
||||
INSTALLED_APPS.remove('debug_toolbar')
|
||||
|
||||
# Middleware
|
||||
|
||||
@@ -29,6 +29,7 @@ from utilities.forms.bulk_import import BulkImportForm
|
||||
from utilities.htmx import htmx_partial
|
||||
from utilities.permissions import get_permission_for_model
|
||||
from utilities.query import reapply_model_ordering
|
||||
from utilities.request import safe_for_redirect
|
||||
from utilities.tables import get_table_configs
|
||||
from utilities.views import GetReturnURLMixin, get_viewname
|
||||
from .base import BaseMultiObjectView
|
||||
@@ -121,7 +122,10 @@ class ObjectListView(BaseMultiObjectView, ActionsMixin, TableMixin):
|
||||
# Strip the `export` param and redirect user to the filtered objects list
|
||||
query_params = request.GET.copy()
|
||||
query_params.pop('export')
|
||||
return redirect(f'{request.path}?{query_params.urlencode()}')
|
||||
redirect_url = f'{request.path}?{query_params.urlencode()}'
|
||||
if safe_for_redirect(redirect_url):
|
||||
return redirect(redirect_url)
|
||||
return redirect(get_viewname(self.queryset.model, 'list'))
|
||||
|
||||
#
|
||||
# Request handlers
|
||||
@@ -286,7 +290,7 @@ class BulkCreateView(GetReturnURLMixin, BaseMultiObjectView):
|
||||
logger.info(msg)
|
||||
messages.success(request, msg)
|
||||
|
||||
if '_addanother' in request.POST:
|
||||
if '_addanother' in request.POST and safe_for_redirect(request.path):
|
||||
return redirect(request.path)
|
||||
return redirect(self.get_return_url(request))
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ from utilities.forms import ConfirmationForm, restrict_form_fields
|
||||
from utilities.htmx import htmx_partial
|
||||
from utilities.permissions import get_permission_for_model
|
||||
from utilities.querydict import normalize_querydict, prepare_cloned_fields
|
||||
from utilities.request import safe_for_redirect
|
||||
from utilities.tables import get_table_configs
|
||||
from utilities.views import GetReturnURLMixin, get_viewname
|
||||
from .base import BaseObjectView
|
||||
@@ -317,6 +318,8 @@ class ObjectEditView(GetReturnURLMixin, BaseObjectView):
|
||||
if 'return_url' in request.GET:
|
||||
params['return_url'] = request.GET.get('return_url')
|
||||
redirect_url += f"?{params.urlencode()}"
|
||||
if not safe_for_redirect(redirect_url):
|
||||
redirect_url = reverse('home')
|
||||
|
||||
return redirect(redirect_url)
|
||||
|
||||
@@ -583,7 +586,7 @@ class ComponentCreateView(GetReturnURLMixin, BaseObjectView):
|
||||
))
|
||||
|
||||
# Redirect user on success
|
||||
if '_addanother' in request.POST:
|
||||
if '_addanother' in request.POST and safe_for_redirect(request.get_full_path()):
|
||||
return redirect(request.get_full_path())
|
||||
else:
|
||||
return redirect(self.get_return_url(request))
|
||||
|
||||
2
netbox/project-static/dist/netbox.css
vendored
2
netbox/project-static/dist/netbox.css
vendored
File diff suppressed because one or more lines are too long
@@ -63,3 +63,27 @@ span.color-label {
|
||||
.sso-icon {
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.btn-white {
|
||||
@extend .btn-light;
|
||||
}
|
||||
|
||||
.btn-black {
|
||||
@extend .btn-dark;
|
||||
}
|
||||
|
||||
.btn-grey, .btn-gray {
|
||||
@extend .btn-secondary;
|
||||
}
|
||||
|
||||
img.plugin-icon {
|
||||
max-width: 1.4285em;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
body[data-bs-theme=dark] {
|
||||
// Assuming icon is black/white line art, invert it and tone down brightness
|
||||
img.plugin-icon {
|
||||
filter: grayscale(100%) invert(100%) brightness(80%);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
// Navbar and light theme styling
|
||||
.navbar-vertical.navbar-expand-lg {
|
||||
|
||||
// Adds spacing to the bottom of the side navigation to avoid hidden nav items
|
||||
@include media-breakpoint-up(lg) {
|
||||
padding-bottom: 2rem;
|
||||
}
|
||||
|
||||
// Adjust hover color & style for menu items
|
||||
.navbar-collapse {
|
||||
.nav-link-icon, .nav-link-title {
|
||||
|
||||
@@ -21,7 +21,7 @@ Blocks:
|
||||
{# Sidebar #}
|
||||
<aside class="navbar navbar-vertical navbar-expand-lg d-print-none">
|
||||
|
||||
{% if 'commercial' in settings.RELEASE.features %}
|
||||
{% if settings.RELEASE.features.commercial %}
|
||||
<img class="motif" src="{% static 'motif.svg' %}" alt="{% trans "NetBox Motif" %}">
|
||||
{% endif %}
|
||||
|
||||
@@ -51,8 +51,19 @@ Blocks:
|
||||
{# Navigation menu #}
|
||||
<div class="collapse navbar-collapse" id="sidebar-menu">
|
||||
{% nav %}
|
||||
</div>
|
||||
|
||||
{# Release info #}
|
||||
<div class="text-muted text-center fs-5 my-3">
|
||||
{{ settings.RELEASE.name }}
|
||||
{% if not settings.RELEASE.features.commercial %}
|
||||
<div>
|
||||
<a href="https://netboxlabs.com/netbox-cloud/" class="text-muted">{% trans "Get" %} Cloud</a> |
|
||||
<a href="https://netboxlabs.com/netbox-enterprise/" class="text-muted">{% trans "Get" %} Enterprise</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
@@ -210,7 +221,6 @@ Blocks:
|
||||
<ul class="list-inline list-inline-dots fs-5 mb-0" id="footer-stamp" hx-swap-oob="true">
|
||||
<li class="list-inline-item">{% now 'Y-m-d H:i:s T' %}</li>
|
||||
<li class="list-inline-item">{{ settings.HOSTNAME }}</li>
|
||||
<li class="list-inline-item">{{ settings.RELEASE.name }}</li>
|
||||
</ul>
|
||||
{# /Footer text #}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Circuit" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -89,7 +89,7 @@
|
||||
{% include 'inc/panels/comments.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
{% include 'circuits/inc/circuit_termination.html' with termination=object.termination_a side='A' %}
|
||||
{% include 'circuits/inc/circuit_termination.html' with termination=object.termination_z side='Z' %}
|
||||
{% include 'inc/panels/image_attachments.html' %}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row mb-3">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Circuit Group" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -46,7 +46,7 @@
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
{% include 'inc/panels/related_objects.html' %}
|
||||
{% include 'inc/panels/comments.html' %}
|
||||
{% include 'inc/panels/custom_fields.html' %}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row mb-3">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Circuit Group Assignment" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -39,7 +39,7 @@
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
{% include 'inc/panels/custom_fields.html' %}
|
||||
{% plugin_right_page object %}
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
|
||||
<div class="card">
|
||||
{% if object %}
|
||||
@@ -37,7 +37,7 @@
|
||||
</div>
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
{% include 'inc/panels/custom_fields.html' %}
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
{% plugin_right_page object %}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row mb-3">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Circuit Type" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -41,7 +41,7 @@
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
{% include 'inc/panels/related_objects.html' %}
|
||||
{% include 'inc/panels/custom_fields.html' %}
|
||||
{% plugin_right_page object %}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row mb-3">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Provider" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -39,7 +39,7 @@
|
||||
{% include 'inc/panels/comments.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
{% include 'inc/panels/related_objects.html' %}
|
||||
{% include 'inc/panels/custom_fields.html' %}
|
||||
{% plugin_right_page object %}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row mb-3">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Provider Account" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -33,7 +33,7 @@
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
{% include 'inc/panels/related_objects.html' %}
|
||||
{% include 'inc/panels/comments.html' %}
|
||||
{% include 'inc/panels/custom_fields.html' %}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row mb-3">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Provider Network" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -37,7 +37,7 @@
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
{% include 'inc/panels/related_objects.html' %}
|
||||
{% include 'inc/panels/comments.html' %}
|
||||
{% include 'inc/panels/custom_fields.html' %}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Virtual circuit" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -61,7 +61,7 @@
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
{% include 'inc/panels/custom_fields.html' %}
|
||||
{% include 'inc/panels/comments.html' %}
|
||||
<div class="card">
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Virtual Circuit Termination" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -48,7 +48,7 @@
|
||||
{% include 'inc/panels/custom_fields.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Interface" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row mb-3">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Virtual Circuit Type" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -41,7 +41,7 @@
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
{% include 'inc/panels/related_objects.html' %}
|
||||
{% include 'inc/panels/custom_fields.html' %}
|
||||
{% plugin_right_page object %}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row mb-3">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Data Source" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -83,7 +83,7 @@
|
||||
{% include 'inc/panels/comments.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Backend" %}</h2>
|
||||
{% with backend=object.backend_class %}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row mb-3">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Job" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -61,7 +61,7 @@
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Scheduling" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-5">
|
||||
<div class="col col-12 col-md-5">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Change" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -73,7 +73,7 @@
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col col-md-7">
|
||||
<div class="col col-12 col-md-7">
|
||||
<div class="card">
|
||||
<h2 class="card-header d-flex justify-content-between">
|
||||
{% trans "Difference" %}
|
||||
@@ -106,7 +106,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Pre-Change Data" %}</h2>
|
||||
<div class="card-body">
|
||||
@@ -126,7 +126,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Post-Change Data" %}</h2>
|
||||
<div class="card-body">
|
||||
@@ -146,10 +146,10 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
{% plugin_right_page object %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Cable" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -63,7 +63,7 @@
|
||||
{% include 'inc/panels/comments.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Termination" %} A</h2>
|
||||
{% include 'dcim/inc/cable_termination.html' with terminations=object.a_terminations %}
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
<td>
|
||||
{% if total_length %}
|
||||
{{ total_length|floatformat:"-2" }}{% if not is_definitive %}+{% endif %} {% trans "Meters" %} /
|
||||
{{ total_length|meters_to_feet|floatformat:"-2" }} {% trans "Feet" %}
|
||||
{{ total_length|meters_to_feet|floatformat:"-2" }}{% if not is_definitive %}+{% endif %} {% trans "Feet" %}
|
||||
{% else %}
|
||||
{{ ''|placeholder }}
|
||||
{% endif %}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Console Port" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -50,7 +50,7 @@
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Connection" %}</h2>
|
||||
{% if object.mark_connected %}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Console Server Port" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -50,7 +50,7 @@
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Connection" %}</h2>
|
||||
<div class="card-body">
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Device Bay" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -38,7 +38,7 @@
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Installed Device" %}</h2>
|
||||
{% if object.installed_device %}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row mb-3">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Device Role" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -53,7 +53,7 @@
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
{% include 'inc/panels/related_objects.html' %}
|
||||
{% include 'inc/panels/custom_fields.html' %}
|
||||
{% include 'inc/panels/comments.html' %}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Chassis" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -96,7 +96,7 @@
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
{% include 'inc/panels/related_objects.html' %}
|
||||
{% include 'inc/panels/custom_fields.html' %}
|
||||
{% include 'inc/panels/comments.html' %}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Front Port" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -64,7 +64,7 @@
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Connection" %}</h2>
|
||||
{% if object.mark_connected %}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row mb-3">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Interface" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -122,7 +122,7 @@
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
{% include 'inc/panel_table.html' with table=vdc_table heading="Virtual Device Contexts" %}
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Addressing" %}</h2>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row mb-3">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Inventory Item" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -70,7 +70,7 @@
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
{% plugin_right_page object %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row mb-3">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Inventory Item Role" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -39,7 +39,7 @@
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
{% include 'inc/panels/custom_fields.html' %}
|
||||
{% plugin_right_page object %}
|
||||
</div>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row mb-3">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Location" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -65,7 +65,7 @@
|
||||
{% include 'inc/panels/comments.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
{% include 'inc/panels/related_objects.html' %}
|
||||
{% include 'inc/panels/image_attachments.html' %}
|
||||
{% plugin_right_page object %}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "MAC Address" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -42,7 +42,7 @@
|
||||
{% include 'inc/panels/custom_fields.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
{% include 'inc/panels/comments.html' %}
|
||||
{% plugin_right_page object %}
|
||||
</div>
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row mb-3">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Manufacturer" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -45,7 +45,7 @@
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
{% include 'inc/panels/related_objects.html' %}
|
||||
{% include 'inc/panels/custom_fields.html' %}
|
||||
{% plugin_right_page object %}
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Module" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -87,7 +87,7 @@
|
||||
{% include 'inc/panels/comments.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Module Type" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Module Bay" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -47,7 +47,7 @@
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
{% include 'inc/panels/custom_fields.html' %}
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Installed Module" %}</h2>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Module Type" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -63,7 +63,7 @@
|
||||
{% include 'inc/panels/comments.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Attributes" %}</h2>
|
||||
{% if not object.profile %}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row mb-3">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Platform" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -46,7 +46,7 @@
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
{% include 'inc/panels/related_objects.html' %}
|
||||
{% include 'inc/panels/custom_fields.html' %}
|
||||
{% plugin_right_page object %}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Power Feed" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -105,7 +105,7 @@
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Connection" %}</h2>
|
||||
{% if object.mark_connected %}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row mb-3">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Power Outlet" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -68,7 +68,7 @@
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Connection" %}</h2>
|
||||
{% if object.mark_connected %}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Power Panel" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -36,7 +36,7 @@
|
||||
{% include 'inc/panels/comments.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
{% include 'inc/panels/related_objects.html' %}
|
||||
{% include 'inc/panels/custom_fields.html' %}
|
||||
{% include 'inc/panels/image_attachments.html' %}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row mb-3">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Power Port" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -54,7 +54,7 @@
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Connection" %}</h2>
|
||||
{% if object.mark_connected %}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row mb-3">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Rack Role" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -37,7 +37,7 @@
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
{% include 'inc/panels/related_objects.html' %}
|
||||
{% include 'inc/panels/custom_fields.html' %}
|
||||
{% plugin_right_page object %}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Rack Type" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -35,7 +35,7 @@
|
||||
{% include 'inc/panels/comments.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
{% include 'dcim/inc/panels/racktype_numbering.html' %}
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Weight" %}</h2>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Rear Port" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -60,7 +60,7 @@
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Connection" %}</h2>
|
||||
{% if object.mark_connected %}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row mb-3">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Region" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -44,7 +44,7 @@
|
||||
{% include 'inc/panels/comments.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
{% include 'inc/panels/related_objects.html' %}
|
||||
{% plugin_right_page object %}
|
||||
</div>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Site" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -114,7 +114,7 @@
|
||||
{% include 'inc/panels/comments.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
{% include 'inc/panels/related_objects.html' with filter_name='site_id' %}
|
||||
{% include 'inc/panels/image_attachments.html' %}
|
||||
{% plugin_right_page object %}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row mb-3">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Site Group" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -44,7 +44,7 @@
|
||||
{% include 'inc/panels/comments.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
{% include 'inc/panels/related_objects.html' %}
|
||||
{% plugin_right_page object %}
|
||||
</div>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-4">
|
||||
<div class="col col-12 col-md-4">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Virtual Chassis" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -47,7 +47,7 @@
|
||||
{% include 'inc/panels/custom_fields.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-8">
|
||||
<div class="col col-12 col-md-8">
|
||||
<div class="card">
|
||||
<h2 class="card-header">
|
||||
{% trans "Members" %}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row mb-3">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Virtual Device Context" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -68,7 +68,7 @@
|
||||
{% plugin_left_page object %}
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
{% include 'inc/panels/related_objects.html' %}
|
||||
{% include 'inc/panels/comments.html' %}
|
||||
{% include 'inc/panels/custom_fields.html' %}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-5">
|
||||
<div class="col col-12 col-md-5">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Config Context" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -76,7 +76,7 @@
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col col-md-7">
|
||||
<div class="col col-12 col-md-7">
|
||||
{% include 'inc/sync_warning.html' %}
|
||||
<div class="card">
|
||||
{% include 'extras/inc/configcontext_data.html' with title="Data" data=object.data format=format copyid="data" %}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Config Template" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -67,7 +67,7 @@
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Environment Parameters" %}</h2>
|
||||
<div class="card-body">
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row mb-3">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Custom Field" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -100,7 +100,7 @@
|
||||
{% include 'inc/panels/comments.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Object Types" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row mb-3">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">Custom Field Choice Set</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -42,7 +42,7 @@
|
||||
</div>
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">Choices ({{ object.choices|length }})</h2>
|
||||
<table class="table table-hover">
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row mb-3">
|
||||
<div class="col col-md-5">
|
||||
<div class="col col-12 col-md-5">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Custom Link" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -47,7 +47,7 @@
|
||||
</div>
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-7">
|
||||
<div class="col col-12 col-md-7">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Link Text" %}</h2>
|
||||
<div class="card-body">
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row mb-3">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Event Rule" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -56,7 +56,7 @@
|
||||
</div>
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Conditions" %}</h2>
|
||||
<div class="card-body">
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Export Template" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Notification Group" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -26,7 +26,7 @@
|
||||
</div>
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Groups" %}</h2>
|
||||
<div class="list-group list-group-flush">
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
{% include 'extras/inc/configcontext_data.html' with title="Rendered Context" data=rendered_context format=format copyid="rendered_context" %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
{% include 'extras/inc/configcontext_data.html' with title="Local Context" data=object.local_context_data format=format copyid="local_context" %}
|
||||
<div class="card-footer">
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row mb-3">
|
||||
<div class="col col-md-5">
|
||||
<div class="col col-12 col-md-5">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Saved Filter" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -47,7 +47,7 @@
|
||||
</div>
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-7">
|
||||
<div class="col col-12 col-md-7">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Parameters" %}</h2>
|
||||
<div class="card-body">
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Tag" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -42,7 +42,7 @@
|
||||
</div>
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Allowed Object Types" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row mb-3">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Webhook" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -55,7 +55,7 @@
|
||||
</div>
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Additional Headers" %}</h2>
|
||||
<div class="card-body">
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Aggregate" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -47,7 +47,7 @@
|
||||
</div>
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
{% include 'inc/panels/custom_fields.html' %}
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
{% include 'inc/panels/comments.html' %}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "ASN" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -47,7 +47,7 @@
|
||||
{% plugin_left_page object %}
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
{% include 'inc/panels/related_objects.html' %}
|
||||
{% include 'inc/panels/custom_fields.html' %}
|
||||
{% include 'inc/panels/comments.html' %}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "ASN Range" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -43,7 +43,7 @@
|
||||
{% plugin_left_page object %}
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
{% include 'inc/panels/custom_fields.html' %}
|
||||
{% plugin_right_page object %}
|
||||
</div>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "FHRP Group" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
@@ -44,7 +44,7 @@
|
||||
{% include 'inc/panels/comments.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="col col-12 col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Authentication" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-4">
|
||||
<div class="col col-12 col-md-4">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "IP Address" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user