Compare commits
18 Commits
5ed81c96dc
...
da7c14d4e0
Author | SHA1 | Date |
---|---|---|
Arthur Hanson | da7c14d4e0 | |
Jeremy Stretch | d606cf1b3c | |
Jeremy Stretch | 0b0dab42eb | |
Jeremy Stretch | d115601da3 | |
Jeremy Stretch | a61e20849b | |
Arthur Hanson | 1eca1c3d17 | |
transifex-integration[bot] | 5d95d49268 | |
Jeremy Stretch | 6b8bfe9947 | |
Jeremy Stretch | e87877b6ea | |
Jeremy Stretch | ebe504c825 | |
Markku Leiniö | b6e38b2ebe | |
Arthur Hanson | 90d0104359 | |
Arthur Hanson | 88facbafbb | |
Jeremy Stretch | c9de3128ca | |
Arthur | 94c31622ac | |
Jeremy Stretch | 3d3c1c315b | |
Jeff Gehlbach | f4c8f5f5b6 | |
Arthur | 397d475159 |
|
@ -26,7 +26,7 @@ body:
|
|||
attributes:
|
||||
label: NetBox Version
|
||||
description: What version of NetBox are you currently running?
|
||||
placeholder: v3.7.5
|
||||
placeholder: v3.7.6
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
|
|
|
@ -14,7 +14,7 @@ body:
|
|||
attributes:
|
||||
label: NetBox version
|
||||
description: What version of NetBox are you currently running?
|
||||
placeholder: v3.7.5
|
||||
placeholder: v3.7.6
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
|
|
|
@ -61,7 +61,8 @@ django-timezone-field
|
|||
|
||||
# A REST API framework for Django projects
|
||||
# https://www.django-rest-framework.org/community/release-notes/
|
||||
djangorestframework
|
||||
# Pinned to 3.14 for NetBox v3.7
|
||||
djangorestframework<3.15
|
||||
|
||||
# Sane and flexible OpenAPI 3 schema generation for Django REST framework.
|
||||
# https://github.com/tfranzel/drf-spectacular/blob/master/CHANGELOG.rst
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
{% block site_meta %}
|
||||
{{ super() }}
|
||||
{# Disable search indexing unless we're building for ReadTheDocs (see #10496) #}
|
||||
{% if page.canonical_url != 'https://docs.netbox.dev/' %}
|
||||
{# Disable search indexing unless we're building for ReadTheDocs #}
|
||||
{% if not config.extra.readthedocs %}
|
||||
<meta name="robots" content="noindex">
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -16,10 +16,7 @@ BASE_PATH = 'netbox/'
|
|||
|
||||
Default: `en-us` (US English)
|
||||
|
||||
Defines the default preferred language/locale for requests that do not specify one. This is used to alter e.g. the display of dates and numbers to fit the user's locale. See [this list](http://www.i18nguy.com/unicode/language-identifiers.html) of standard language codes. (This parameter maps to Django's [`LANGUAGE_CODE`](https://docs.djangoproject.com/en/stable/ref/settings/#language-code) internal setting.)
|
||||
|
||||
!!! note
|
||||
Altering this parameter will *not* change the language used in NetBox. We hope to provide translation support in a future NetBox release.
|
||||
Defines the default preferred language/locale for requests that do not specify one. (This parameter maps to Django's [`LANGUAGE_CODE`](https://docs.djangoproject.com/en/stable/ref/settings/#language-code) internal setting.)
|
||||
|
||||
---
|
||||
|
||||
|
|
|
@ -62,7 +62,7 @@ class MyModelImportForm(NetBoxModelImportForm):
|
|||
site = CSVModelChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='name',
|
||||
help_text='Assigned site'
|
||||
help_text=_('Assigned site')
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
|
|
@ -3,6 +3,9 @@
|
|||
!!! tip "Plugins Development Tutorial"
|
||||
Just getting started with plugins? Check out our [**NetBox Plugin Tutorial**](https://github.com/netbox-community/netbox-plugin-tutorial) on GitHub! This in-depth guide will walk you through the process of creating an entire plugin from scratch. It even includes a companion [demo plugin repo](https://github.com/netbox-community/netbox-plugin-demo) to ensure you can jump in at any step along the way. This will get you up and running with plugins in no time!
|
||||
|
||||
!!! tip "Plugin Certification Program"
|
||||
NetBox Labs offers a [**Plugin Certification Program**](https://github.com/netbox-community/netbox/wiki/Plugin-Certification-Program) for plugin developers interested in establishing a co-maintainer relationship. The program aims to assure ongoing compatibility, maintainability, and commercial supportability of key plugins.
|
||||
|
||||
NetBox can be extended to support additional data models and functionality through the use of plugins. A plugin is essentially a self-contained [Django app](https://docs.djangoproject.com/en/stable/) which gets installed alongside NetBox to provide custom functionality. Multiple plugins can be installed in a single NetBox instance, and each plugin can be enabled and configured independently.
|
||||
|
||||
!!! info "Django Development"
|
||||
|
|
|
@ -1,20 +1,30 @@
|
|||
# NetBox v3.7
|
||||
|
||||
## v3.7.6 (FUTURE)
|
||||
## v3.7.7 (FUTURE)
|
||||
|
||||
---
|
||||
|
||||
## v3.7.6 (2024-04-22)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* [#14690](https://github.com/netbox-community/netbox/issues/14690) - Improve rendering of JSON data in configuration form
|
||||
* [#15427](https://github.com/netbox-community/netbox/issues/15427) - Enable compatibility with non-Amazon S3 providers for remote data sources
|
||||
* [#15640](https://github.com/netbox-community/netbox/issues/15640) - Add global search support for L2VPN identifiers
|
||||
* [#15644](https://github.com/netbox-community/netbox/issues/15644) - Introduce new configuration parameters for enabling HTTP Strict Transport Security (HSTS)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* [#15541](https://github.com/netbox-community/netbox/issues/15541) - Restore ability to modify assigned component template when adding/modifying an inventory item template
|
||||
* [#15582](https://github.com/netbox-community/netbox/issues/15582) - Fix permission constraints for synchronization of remote data sources
|
||||
* [#15588](https://github.com/netbox-community/netbox/issues/15588) - Correct OpenAPI schema definitions for read-only fields which may return null values
|
||||
* [#15635](https://github.com/netbox-community/netbox/issues/15635) - Extend plugin removal instruction to include reindexing the global search cache
|
||||
* [#15654](https://github.com/netbox-community/netbox/issues/15654) - Fix `AttributeError` exception when attempting to save an incomplete tunnel termination
|
||||
* [#15668](https://github.com/netbox-community/netbox/issues/15668) - Fix permission required to display virtual disks tab on virtual machine UI view
|
||||
* [#15685](https://github.com/netbox-community/netbox/issues/15685) - Allow filtering cables by decimal values using UI filter form
|
||||
* [#15761](https://github.com/netbox-community/netbox/issues/15761) - Add missing `ike_policy` & `ike_policy_id` filters for IKE proposals
|
||||
* [#15771](https://github.com/netbox-community/netbox/issues/15771) - Include `id` in list of supported fields for all bulk import forms
|
||||
* [#15790](https://github.com/netbox-community/netbox/issues/15790) - Fix live preview support for EventRule comments
|
||||
|
||||
---
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@ plugins:
|
|||
show_root_toc_entry: false
|
||||
show_source: false
|
||||
extra:
|
||||
readthedocs: !ENV READTHEDOCS
|
||||
social:
|
||||
- icon: fontawesome/brands/github
|
||||
link: https://github.com/netbox-community/netbox
|
||||
|
|
|
@ -47,6 +47,13 @@ class ChoiceFieldFix(OpenApiSerializerFieldExtension):
|
|||
)
|
||||
|
||||
|
||||
class SerializedPKRelatedFieldFix(OpenApiSerializerFieldExtension):
|
||||
target_class = "netbox.api.fields.SerializedPKRelatedField"
|
||||
|
||||
def map_serializer_field(self, auto_schema, direction):
|
||||
return auto_schema._map_serializer(self.target.serializer, direction)
|
||||
|
||||
|
||||
class NetBoxAutoSchema(AutoSchema):
|
||||
"""
|
||||
Overrides to drf_spectacular.openapi.AutoSchema to fix following issues:
|
||||
|
|
|
@ -3,6 +3,7 @@ import json
|
|||
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.forms.fields import JSONField as _JSONField
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from core.forms.mixins import SyncedDataMixin
|
||||
|
@ -12,7 +13,7 @@ from netbox.forms import NetBoxModelForm
|
|||
from netbox.registry import registry
|
||||
from netbox.utils import get_data_backend_choices
|
||||
from utilities.forms import BootstrapMixin, get_field_value
|
||||
from utilities.forms.fields import CommentField
|
||||
from utilities.forms.fields import CommentField, JSONField
|
||||
from utilities.forms.widgets import HTMXSelect
|
||||
|
||||
__all__ = (
|
||||
|
@ -132,6 +133,9 @@ class ConfigFormMetaclass(forms.models.ModelFormMetaclass):
|
|||
'help_text': param.description,
|
||||
}
|
||||
field_kwargs.update(**param.field_kwargs)
|
||||
if param.field is _JSONField:
|
||||
# Replace with our own JSONField to get pretty JSON in config editor
|
||||
param.field = JSONField
|
||||
param_fields[param.name] = param.field(**field_kwargs)
|
||||
attrs.update(param_fields)
|
||||
|
||||
|
|
|
@ -612,7 +612,7 @@ class InventoryItemTemplateSerializer(ValidatedModelSerializer):
|
|||
required=False,
|
||||
allow_null=True
|
||||
)
|
||||
component = serializers.SerializerMethodField(read_only=True)
|
||||
component = serializers.SerializerMethodField(read_only=True, allow_null=True)
|
||||
_depth = serializers.IntegerField(source='level', read_only=True)
|
||||
|
||||
class Meta:
|
||||
|
@ -668,7 +668,7 @@ class DeviceSerializer(NetBoxModelSerializer):
|
|||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:device-detail')
|
||||
device_type = NestedDeviceTypeSerializer()
|
||||
role = NestedDeviceRoleSerializer()
|
||||
device_role = NestedDeviceRoleSerializer(read_only=True, help_text='Deprecated in v3.6 in favor of `role`.')
|
||||
device_role = NestedDeviceRoleSerializer(read_only=True, help_text=_('Deprecated in v3.6 in favor of `role`.'))
|
||||
tenant = NestedTenantSerializer(required=False, allow_null=True, default=None)
|
||||
platform = NestedPlatformSerializer(required=False, allow_null=True)
|
||||
site = NestedSiteSerializer()
|
||||
|
@ -685,7 +685,7 @@ class DeviceSerializer(NetBoxModelSerializer):
|
|||
)
|
||||
status = ChoiceField(choices=DeviceStatusChoices, required=False)
|
||||
airflow = ChoiceField(choices=DeviceAirflowChoices, allow_blank=True, required=False)
|
||||
primary_ip = NestedIPAddressSerializer(read_only=True)
|
||||
primary_ip = NestedIPAddressSerializer(read_only=True, allow_null=True)
|
||||
primary_ip4 = NestedIPAddressSerializer(required=False, allow_null=True)
|
||||
primary_ip6 = NestedIPAddressSerializer(required=False, allow_null=True)
|
||||
oob_ip = NestedIPAddressSerializer(required=False, allow_null=True)
|
||||
|
@ -735,7 +735,7 @@ class DeviceSerializer(NetBoxModelSerializer):
|
|||
|
||||
|
||||
class DeviceWithConfigContextSerializer(DeviceSerializer):
|
||||
config_context = serializers.SerializerMethodField(read_only=True)
|
||||
config_context = serializers.SerializerMethodField(read_only=True, allow_null=True)
|
||||
|
||||
class Meta(DeviceSerializer.Meta):
|
||||
fields = [
|
||||
|
@ -1067,7 +1067,7 @@ class InventoryItemSerializer(NetBoxModelSerializer):
|
|||
required=False,
|
||||
allow_null=True
|
||||
)
|
||||
component = serializers.SerializerMethodField(read_only=True)
|
||||
component = serializers.SerializerMethodField(read_only=True, allow_null=True)
|
||||
_depth = serializers.IntegerField(source='level', read_only=True)
|
||||
|
||||
class Meta:
|
||||
|
|
|
@ -1373,14 +1373,14 @@ class VirtualDeviceContextImportForm(NetBoxModelImportForm):
|
|||
label=_('Device'),
|
||||
queryset=Device.objects.all(),
|
||||
to_field_name='name',
|
||||
help_text='Assigned role'
|
||||
help_text=_('Assigned role')
|
||||
)
|
||||
tenant = CSVModelChoiceField(
|
||||
label=_('Tenant'),
|
||||
queryset=Tenant.objects.all(),
|
||||
required=False,
|
||||
to_field_name='name',
|
||||
help_text='Assigned tenant'
|
||||
help_text=_('Assigned tenant')
|
||||
)
|
||||
status = CSVChoiceField(
|
||||
label=_('Status'),
|
||||
|
|
|
@ -976,21 +976,67 @@ class InventoryItemTemplateForm(ComponentTemplateForm):
|
|||
queryset=Manufacturer.objects.all(),
|
||||
required=False
|
||||
)
|
||||
component_type = ContentTypeChoiceField(
|
||||
queryset=ContentType.objects.all(),
|
||||
limit_choices_to=MODULAR_COMPONENT_TEMPLATE_MODELS,
|
||||
# Assigned component selectors
|
||||
consoleporttemplate = DynamicModelChoiceField(
|
||||
queryset=ConsolePortTemplate.objects.all(),
|
||||
required=False,
|
||||
widget=forms.HiddenInput
|
||||
query_params={
|
||||
'device_type_id': '$device_type'
|
||||
},
|
||||
label=_('Console port template')
|
||||
)
|
||||
component_id = forms.IntegerField(
|
||||
consoleserverporttemplate = DynamicModelChoiceField(
|
||||
queryset=ConsoleServerPortTemplate.objects.all(),
|
||||
required=False,
|
||||
widget=forms.HiddenInput
|
||||
query_params={
|
||||
'device_type_id': '$device_type'
|
||||
},
|
||||
label=_('Console server port template')
|
||||
)
|
||||
frontporttemplate = DynamicModelChoiceField(
|
||||
queryset=FrontPortTemplate.objects.all(),
|
||||
required=False,
|
||||
query_params={
|
||||
'device_type_id': '$device_type'
|
||||
},
|
||||
label=_('Front port template')
|
||||
)
|
||||
interfacetemplate = DynamicModelChoiceField(
|
||||
queryset=InterfaceTemplate.objects.all(),
|
||||
required=False,
|
||||
query_params={
|
||||
'device_type_id': '$device_type'
|
||||
},
|
||||
label=_('Interface template')
|
||||
)
|
||||
poweroutlettemplate = DynamicModelChoiceField(
|
||||
queryset=PowerOutletTemplate.objects.all(),
|
||||
required=False,
|
||||
query_params={
|
||||
'device_type_id': '$device_type'
|
||||
},
|
||||
label=_('Power outlet template')
|
||||
)
|
||||
powerporttemplate = DynamicModelChoiceField(
|
||||
queryset=PowerPortTemplate.objects.all(),
|
||||
required=False,
|
||||
query_params={
|
||||
'device_type_id': '$device_type'
|
||||
},
|
||||
label=_('Power port template')
|
||||
)
|
||||
rearporttemplate = DynamicModelChoiceField(
|
||||
queryset=RearPortTemplate.objects.all(),
|
||||
required=False,
|
||||
query_params={
|
||||
'device_type_id': '$device_type'
|
||||
},
|
||||
label=_('Rear port template')
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
(None, (
|
||||
'device_type', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'description',
|
||||
'component_type', 'component_id',
|
||||
)),
|
||||
)
|
||||
|
||||
|
@ -998,9 +1044,52 @@ class InventoryItemTemplateForm(ComponentTemplateForm):
|
|||
model = InventoryItemTemplate
|
||||
fields = [
|
||||
'device_type', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'description',
|
||||
'component_type', 'component_id',
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
instance = kwargs.get('instance')
|
||||
initial = kwargs.get('initial', {}).copy()
|
||||
component_type = initial.get('component_type')
|
||||
component_id = initial.get('component_id')
|
||||
|
||||
# Used for picking the default active tab for component selection
|
||||
self.no_component = True
|
||||
|
||||
if instance:
|
||||
# When editing set the initial value for component selection
|
||||
for component_model in ContentType.objects.filter(MODULAR_COMPONENT_TEMPLATE_MODELS):
|
||||
if type(instance.component) is component_model.model_class():
|
||||
initial[component_model.model] = instance.component
|
||||
self.no_component = False
|
||||
break
|
||||
elif component_type and component_id:
|
||||
# When adding the InventoryItem from a component page
|
||||
if content_type := ContentType.objects.filter(MODULAR_COMPONENT_TEMPLATE_MODELS).filter(pk=component_type).first():
|
||||
if component := content_type.model_class().objects.filter(pk=component_id).first():
|
||||
initial[content_type.model] = component
|
||||
self.no_component = False
|
||||
|
||||
kwargs['initial'] = initial
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
|
||||
# Handle object assignment
|
||||
selected_objects = [
|
||||
field for field in (
|
||||
'consoleporttemplate', 'consoleserverporttemplate', 'frontporttemplate', 'interfacetemplate',
|
||||
'poweroutlettemplate', 'powerporttemplate', 'rearporttemplate'
|
||||
) if self.cleaned_data[field]
|
||||
]
|
||||
if len(selected_objects) > 1:
|
||||
raise forms.ValidationError(_("An InventoryItem can only be assigned to a single component."))
|
||||
elif selected_objects:
|
||||
self.instance.component = self.cleaned_data[selected_objects[0]]
|
||||
else:
|
||||
self.instance.component = None
|
||||
|
||||
|
||||
#
|
||||
# Device components
|
||||
|
|
|
@ -1656,6 +1656,7 @@ class InventoryItemTemplateCreateView(generic.ComponentCreateView):
|
|||
queryset = InventoryItemTemplate.objects.all()
|
||||
form = forms.InventoryItemTemplateCreateForm
|
||||
model_form = forms.InventoryItemTemplateForm
|
||||
template_name = 'dcim/inventoryitemtemplate_edit.html'
|
||||
|
||||
def alter_object(self, instance, request):
|
||||
# Set component (if any)
|
||||
|
@ -1673,6 +1674,7 @@ class InventoryItemTemplateCreateView(generic.ComponentCreateView):
|
|||
class InventoryItemTemplateEditView(generic.ObjectEditView):
|
||||
queryset = InventoryItemTemplate.objects.all()
|
||||
form = forms.InventoryItemTemplateForm
|
||||
template_name = 'dcim/inventoryitemtemplate_edit.html'
|
||||
|
||||
|
||||
@register_model_view(InventoryItemTemplate, 'delete')
|
||||
|
|
|
@ -265,6 +265,7 @@ class EventRuleForm(NetBoxModelForm):
|
|||
required=False,
|
||||
help_text=_('Enter parameters to pass to the action in <a href="https://json.org/">JSON</a> format.')
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
(_('Event Rule'), ('name', 'description', 'content_types', 'enabled', 'tags')),
|
||||
|
|
|
@ -262,7 +262,7 @@ class AvailableVLANSerializer(serializers.Serializer):
|
|||
Representation of a VLAN which does not exist in the database.
|
||||
"""
|
||||
vid = serializers.IntegerField(read_only=True)
|
||||
group = NestedVLANGroupSerializer(read_only=True)
|
||||
group = NestedVLANGroupSerializer(read_only=True, allow_null=True)
|
||||
|
||||
def to_representation(self, instance):
|
||||
return {
|
||||
|
@ -348,9 +348,9 @@ class AvailablePrefixSerializer(serializers.Serializer):
|
|||
"""
|
||||
Representation of a prefix which does not exist in the database.
|
||||
"""
|
||||
family = serializers.IntegerField(read_only=True)
|
||||
family = serializers.IntegerField(read_only=True, allow_null=True)
|
||||
prefix = serializers.CharField(read_only=True)
|
||||
vrf = NestedVRFSerializer(read_only=True)
|
||||
vrf = NestedVRFSerializer(read_only=True, allow_null=True)
|
||||
|
||||
def to_representation(self, instance):
|
||||
if self.context.get('vrf'):
|
||||
|
@ -429,9 +429,9 @@ class AvailableIPSerializer(serializers.Serializer):
|
|||
"""
|
||||
Representation of an IP address which does not exist in the database.
|
||||
"""
|
||||
family = serializers.IntegerField(read_only=True)
|
||||
family = serializers.IntegerField(read_only=True, allow_null=True)
|
||||
address = serializers.CharField(read_only=True)
|
||||
vrf = NestedVRFSerializer(read_only=True)
|
||||
vrf = NestedVRFSerializer(read_only=True, allow_null=True)
|
||||
description = serializers.CharField(required=False)
|
||||
|
||||
def to_representation(self, instance):
|
||||
|
|
|
@ -73,17 +73,12 @@ class NetBoxModelImportForm(CSVModelForm, NetBoxModelForm):
|
|||
"""
|
||||
Base form for creating a NetBox objects from CSV data. Used for bulk importing.
|
||||
"""
|
||||
id = forms.IntegerField(
|
||||
label=_('Id'),
|
||||
required=False,
|
||||
help_text='Numeric ID of an existing object to update (if not creating a new object)'
|
||||
)
|
||||
tags = CSVModelMultipleChoiceField(
|
||||
label=_('Tags'),
|
||||
queryset=Tag.objects.all(),
|
||||
required=False,
|
||||
to_field_name='slug',
|
||||
help_text='Tag slugs separated by commas, encased with double quotes (e.g. "tag1,tag2,tag3")'
|
||||
help_text=_('Tag slugs separated by commas, encased with double quotes (e.g. "tag1,tag2,tag3")')
|
||||
)
|
||||
|
||||
def _get_custom_fields(self, content_type):
|
||||
|
|
|
@ -28,7 +28,7 @@ from netbox.plugins import PluginConfig
|
|||
# Environment setup
|
||||
#
|
||||
|
||||
VERSION = '3.7.6-dev'
|
||||
VERSION = '3.7.7-dev'
|
||||
|
||||
# Hostname
|
||||
HOSTNAME = platform.node()
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
{% extends 'generic/object_edit.html' %}
|
||||
{% load static %}
|
||||
{% load form_helpers %}
|
||||
{% load helpers %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block form %}
|
||||
<div class="field-group my-5">
|
||||
<div class="row mb-2">
|
||||
<h5 class="offset-sm-3">{% trans "Inventory Item" %}</h5>
|
||||
</div>
|
||||
{% render_field form.device_type %}
|
||||
{% render_field form.parent %}
|
||||
{% render_field form.name %}
|
||||
{% render_field form.label %}
|
||||
{% render_field form.role %}
|
||||
{% render_field form.description %}
|
||||
</div>
|
||||
|
||||
<div class="field-group my-5">
|
||||
<div class="row mb-2">
|
||||
<h5 class="offset-sm-3">{% trans "Hardware" %}</h5>
|
||||
</div>
|
||||
{% render_field form.manufacturer %}
|
||||
{% render_field form.part_id %}
|
||||
</div>
|
||||
|
||||
<div class="field-group my-5">
|
||||
<div class="row mb-2">
|
||||
<h5 class="offset-sm-3">{% trans "Component Assignment" %}</h5>
|
||||
</div>
|
||||
<div class="row mb-2 offset-sm-3">
|
||||
<ul class="nav nav-pills" role="tablist">
|
||||
<li role="presentation" class="nav-item">
|
||||
<button role="tab" type="button" id="consoleport_tab" data-bs-toggle="tab" aria-controls="consoleport" data-bs-target="#consoleport" class="nav-link {% if form.initial.consoleporttemplate or form.no_component %}active{% endif %}">
|
||||
{% trans "Console Port" %}
|
||||
</button>
|
||||
</li>
|
||||
<li role="presentation" class="nav-item">
|
||||
<button role="tab" type="button" id="consoleserverport_tab" data-bs-toggle="tab" aria-controls="consoleserverport" data-bs-target="#consoleserverport" class="nav-link {% if form.initial.consoleserverporttemplate %}active{% endif %}">
|
||||
{% trans "Console Server Port" %}
|
||||
</button>
|
||||
</li>
|
||||
<li role="presentation" class="nav-item">
|
||||
<button role="tab" type="button" id="frontport_tab" data-bs-toggle="tab" aria-controls="frontport" data-bs-target="#frontport" class="nav-link {% if form.initial.frontporttemplate %}active{% endif %}">
|
||||
{% trans "Front Port" %}
|
||||
</button>
|
||||
</li>
|
||||
<li role="presentation" class="nav-item">
|
||||
<button role="tab" type="button" id="interface_tab" data-bs-toggle="tab" aria-controls="interface" data-bs-target="#interface" class="nav-link {% if form.initial.interfacetemplate %}active{% endif %}">
|
||||
{% trans "Interface" %}
|
||||
</button>
|
||||
</li>
|
||||
<li role="presentation" class="nav-item">
|
||||
<button role="tab" type="button" id="poweroutlet_tab" data-bs-toggle="tab" aria-controls="poweroutlet" data-bs-target="#poweroutlet" class="nav-link {% if form.initial.poweroutlettemplate %}active{% endif %}">
|
||||
{% trans "Power Outlet" %}
|
||||
</button>
|
||||
</li>
|
||||
<li role="presentation" class="nav-item">
|
||||
<button role="tab" type="button" id="powerport_tab" data-bs-toggle="tab" aria-controls="powerport" data-bs-target="#powerport" class="nav-link {% if form.initial.powerporttemplate %}active{% endif %}">
|
||||
{% trans "Power Port" %}
|
||||
</button>
|
||||
</li>
|
||||
<li role="presentation" class="nav-item">
|
||||
<button role="tab" type="button" id="rearport_tab" data-bs-toggle="tab" aria-controls="rearport" data-bs-target="#rearport" class="nav-link {% if form.initial.rearporttemplate %}active{% endif %}">
|
||||
{% trans "Rear Port" %}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content p-0 border-0">
|
||||
<div class="tab-pane {% if form.initial.consoleporttemplate or form.no_component %}active{% endif %}" id="consoleport" role="tabpanel" aria-labeled-by="consoleport_tab">
|
||||
{% render_field form.consoleporttemplate %}
|
||||
</div>
|
||||
<div class="tab-pane {% if form.initial.consoleserverporttemplate %}active{% endif %}" id="consoleserverport" role="tabpanel" aria-labeled-by="consoleserverport_tab">
|
||||
{% render_field form.consoleserverporttemplate %}
|
||||
</div>
|
||||
<div class="tab-pane {% if form.initial.frontporttemplate %}active{% endif %}" id="frontport" role="tabpanel" aria-labeled-by="frontport_tab">
|
||||
{% render_field form.frontporttemplate %}
|
||||
</div>
|
||||
<div class="tab-pane {% if form.initial.interfacetemplate %}active{% endif %}" id="interface" role="tabpanel" aria-labeled-by="interface_tab">
|
||||
{% render_field form.interfacetemplate %}
|
||||
</div>
|
||||
<div class="tab-pane {% if form.initial.poweroutlettemplate %}active{% endif %}" id="poweroutlet" role="tabpanel" aria-labeled-by="poweroutlet_tab">
|
||||
{% render_field form.poweroutlettemplate %}
|
||||
</div>
|
||||
<div class="tab-pane {% if form.initial.powerporttemplate %}active{% endif %}" id="powerport" role="tabpanel" aria-labeled-by="powerport_tab">
|
||||
{% render_field form.powerporttemplate %}
|
||||
</div>
|
||||
<div class="tab-pane {% if form.initial.rearporttemplate %}active{% endif %}" id="rearport" role="tabpanel" aria-labeled-by="rearport_tab">
|
||||
{% render_field form.rearporttemplate %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if form.custom_fields %}
|
||||
<div class="field-group my-5">
|
||||
<div class="row mb-2">
|
||||
<h5 class="offset-sm-3">{% trans "Custom Fields" %}</h5>
|
||||
</div>
|
||||
{% render_custom_fields form %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
@ -6,6 +6,7 @@
|
|||
# Translators:
|
||||
# Jonathan Senecal, 2024
|
||||
# Jeremy Stretch, 2024
|
||||
# Quentin Laurent, 2024
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
|
@ -14,7 +15,7 @@ msgstr ""
|
|||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-04-04 19:11+0000\n"
|
||||
"PO-Revision-Date: 2023-10-30 17:48+0000\n"
|
||||
"Last-Translator: Jeremy Stretch, 2024\n"
|
||||
"Last-Translator: Quentin Laurent, 2024\n"
|
||||
"Language-Team: French (https://app.transifex.com/netbox-community/teams/178115/fr/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
@ -3716,7 +3717,7 @@ msgstr "Réservation"
|
|||
#: dcim/forms/model_forms.py:301 dcim/forms/model_forms.py:384
|
||||
#: utilities/forms/fields/fields.py:47
|
||||
msgid "Slug"
|
||||
msgstr "limace"
|
||||
msgstr "Identifiant"
|
||||
|
||||
#: dcim/forms/model_forms.py:308 templates/dcim/devicetype.html:12
|
||||
msgid "Chassis"
|
||||
|
@ -5813,7 +5814,7 @@ msgstr "Poids maximum"
|
|||
#: ipam/tables/asn.py:66 netbox/navigation/menu.py:16
|
||||
#: netbox/navigation/menu.py:18
|
||||
msgid "Sites"
|
||||
msgstr "Des sites"
|
||||
msgstr "Sites"
|
||||
|
||||
#: dcim/tests/test_api.py:49
|
||||
msgid "Test case must set peer_termination_type"
|
||||
|
@ -13355,7 +13356,7 @@ msgstr ""
|
|||
|
||||
#: utilities/forms/fields/fields.py:48
|
||||
msgid "URL-friendly unique shorthand"
|
||||
msgstr "Raccourci unique et convivial pour les URL"
|
||||
msgstr "Identifiant unique utilisable dans les URL"
|
||||
|
||||
#: utilities/forms/fields/fields.py:101
|
||||
msgid "Enter context data in <a href=\"https://json.org/\">JSON</a> format."
|
||||
|
|
Binary file not shown.
|
@ -5,8 +5,8 @@
|
|||
#
|
||||
# Translators:
|
||||
# Tatsuya Ueda <ml@tatsuya.info>, 2024
|
||||
# teapot, 2024
|
||||
# Jeremy Stretch, 2024
|
||||
# teapot, 2024
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
|
@ -15,7 +15,7 @@ msgstr ""
|
|||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-04-04 19:11+0000\n"
|
||||
"PO-Revision-Date: 2023-10-30 17:48+0000\n"
|
||||
"Last-Translator: Jeremy Stretch, 2024\n"
|
||||
"Last-Translator: teapot, 2024\n"
|
||||
"Language-Team: Japanese (https://app.transifex.com/netbox-community/teams/178115/ja/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
@ -7681,7 +7681,7 @@ msgstr "プレフィックス内およびプレフィックスを含む"
|
|||
|
||||
#: ipam/filtersets.py:259
|
||||
msgid "Prefixes which contain this prefix or IP"
|
||||
msgstr "このプレフィックスまたは IP を含むプレフィックス"
|
||||
msgstr "このプレフィックス / IP を含むプレフィックス"
|
||||
|
||||
#: ipam/filtersets.py:270 ipam/filtersets.py:538 ipam/forms/bulk_edit.py:326
|
||||
#: ipam/forms/filtersets.py:191 ipam/forms/filtersets.py:317
|
||||
|
@ -7700,11 +7700,11 @@ msgstr "VLAN 番号 (1-4094)"
|
|||
#: ipam/forms/model_forms.py:430 templates/tenancy/contact.html:54
|
||||
#: tenancy/forms/bulk_edit.py:112
|
||||
msgid "Address"
|
||||
msgstr "住所"
|
||||
msgstr "アドレス"
|
||||
|
||||
#: ipam/filtersets.py:445
|
||||
msgid "Ranges which contain this prefix or IP"
|
||||
msgstr "このプレフィックスまたは IP を含む範囲"
|
||||
msgstr "このプレフィックス / IP を含む範囲"
|
||||
|
||||
#: ipam/filtersets.py:473 ipam/filtersets.py:529
|
||||
msgid "Parent prefix"
|
||||
|
@ -7743,11 +7743,11 @@ msgstr "FHRP グループ (ID)"
|
|||
|
||||
#: ipam/filtersets.py:618
|
||||
msgid "Is assigned to an interface"
|
||||
msgstr "インタフェースに割り当てられている"
|
||||
msgstr "インタフェースに割り当てられているか"
|
||||
|
||||
#: ipam/filtersets.py:622
|
||||
msgid "Is assigned"
|
||||
msgstr "割り当てられている"
|
||||
msgstr "割当済みか"
|
||||
|
||||
#: ipam/filtersets.py:1047
|
||||
msgid "IP address (ID)"
|
||||
|
@ -7881,7 +7881,7 @@ msgstr "子 VLAN VID の最小値"
|
|||
|
||||
#: ipam/forms/bulk_edit.py:420
|
||||
msgid "Maximum child VLAN VID"
|
||||
msgstr "子 VLAN VID の最大数"
|
||||
msgstr "子 VLAN VID の最大値"
|
||||
|
||||
#: ipam/forms/bulk_edit.py:428 ipam/forms/model_forms.py:531
|
||||
msgid "Scope type"
|
||||
|
@ -7905,11 +7905,11 @@ msgstr "ポート"
|
|||
|
||||
#: ipam/forms/bulk_import.py:47
|
||||
msgid "Import route targets"
|
||||
msgstr "ルートターゲットをインポート"
|
||||
msgstr "インポートルートターゲット"
|
||||
|
||||
#: ipam/forms/bulk_import.py:53
|
||||
msgid "Export route targets"
|
||||
msgstr "ルートターゲットをエクスポートする"
|
||||
msgstr "エクスポートルートターゲット"
|
||||
|
||||
#: ipam/forms/bulk_import.py:91 ipam/forms/bulk_import.py:111
|
||||
#: ipam/forms/bulk_import.py:131
|
||||
|
|
|
@ -70,6 +70,12 @@ class CSVModelForm(forms.ModelForm):
|
|||
"""
|
||||
ModelForm used for the import of objects in CSV format.
|
||||
"""
|
||||
id = forms.IntegerField(
|
||||
label=_('ID'),
|
||||
required=False,
|
||||
help_text=_('Numeric ID of an existing object to update (if not creating a new object)')
|
||||
)
|
||||
|
||||
def __init__(self, *args, headers=None, **kwargs):
|
||||
self.headers = headers or {}
|
||||
super().__init__(*args, **kwargs)
|
||||
|
|
|
@ -76,7 +76,7 @@ class VirtualMachineSerializer(NetBoxModelSerializer):
|
|||
role = NestedDeviceRoleSerializer(required=False, allow_null=True)
|
||||
tenant = NestedTenantSerializer(required=False, allow_null=True)
|
||||
platform = NestedPlatformSerializer(required=False, allow_null=True)
|
||||
primary_ip = NestedIPAddressSerializer(read_only=True)
|
||||
primary_ip = NestedIPAddressSerializer(read_only=True, allow_null=True)
|
||||
primary_ip4 = NestedIPAddressSerializer(required=False, allow_null=True)
|
||||
primary_ip6 = NestedIPAddressSerializer(required=False, allow_null=True)
|
||||
config_template = NestedConfigTemplateSerializer(required=False, allow_null=True, default=None)
|
||||
|
|
|
@ -136,6 +136,17 @@ class IKEProposalFilterSet(NetBoxModelFilterSet):
|
|||
group = django_filters.MultipleChoiceFilter(
|
||||
choices=DHGroupChoices
|
||||
)
|
||||
ike_policy_id = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='ike_policies',
|
||||
queryset=IKEPolicy.objects.all(),
|
||||
label=_('IKE policy (ID)'),
|
||||
)
|
||||
ike_policy = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='ike_policies__name',
|
||||
queryset=IKEPolicy.objects.all(),
|
||||
to_field_name='name',
|
||||
label=_('IKE policy (name)'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = IKEProposal
|
||||
|
|
|
@ -331,6 +331,16 @@ class IKEProposalTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||
)
|
||||
IKEProposal.objects.bulk_create(ike_proposals)
|
||||
|
||||
ike_policies = (
|
||||
IKEPolicy(name='IKE Policy 1'),
|
||||
IKEPolicy(name='IKE Policy 2'),
|
||||
IKEPolicy(name='IKE Policy 3'),
|
||||
)
|
||||
IKEPolicy.objects.bulk_create(ike_policies)
|
||||
ike_policies[0].proposals.add(ike_proposals[0])
|
||||
ike_policies[1].proposals.add(ike_proposals[1])
|
||||
ike_policies[2].proposals.add(ike_proposals[2])
|
||||
|
||||
def test_q(self):
|
||||
params = {'q': 'foobar1'}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
|
@ -369,6 +379,13 @@ class IKEProposalTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||
params = {'sa_lifetime': [1000, 2000]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_ike_policy(self):
|
||||
ike_policies = IKEPolicy.objects.all()[:2]
|
||||
params = {'ike_policy_id': [ike_policies[0].pk, ike_policies[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'ike_policy': [ike_policies[0].name, ike_policies[1].name]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
|
||||
class IKEPolicyTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
queryset = IKEPolicy.objects.all()
|
||||
|
|
|
@ -42,7 +42,7 @@ class WirelessLANImportForm(NetBoxModelImportForm):
|
|||
status = CSVChoiceField(
|
||||
label=_('Status'),
|
||||
choices=WirelessLANStatusChoices,
|
||||
help_text='Operational status'
|
||||
help_text=_('Operational status')
|
||||
)
|
||||
vlan = CSVModelChoiceField(
|
||||
label=_('VLAN'),
|
||||
|
|
|
@ -18,11 +18,11 @@ drf-spectacular==0.27.2
|
|||
drf-spectacular-sidecar==2024.4.1
|
||||
feedparser==6.0.11
|
||||
graphene-django==3.0.0
|
||||
gunicorn==21.2.0
|
||||
gunicorn==22.0.0
|
||||
Jinja2==3.1.3
|
||||
Markdown==3.6
|
||||
mkdocs-material==9.5.17
|
||||
mkdocstrings[python-legacy]==0.24.2
|
||||
mkdocs-material==9.5.18
|
||||
mkdocstrings[python-legacy]==0.24.3
|
||||
netaddr==1.2.1
|
||||
Pillow==10.3.0
|
||||
psycopg[binary,pool]==3.1.18
|
||||
|
|
Loading…
Reference in New Issue