mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-16 04:02:52 -06:00
Merge branch 'develop' into fix/generic_prefetch_4.2
This commit is contained in:
commit
d226af420b
3
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
3
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@ -34,10 +34,9 @@ body:
|
||||
label: Python Version
|
||||
description: What version of Python are you currently running?
|
||||
options:
|
||||
- "3.8"
|
||||
- "3.9"
|
||||
- "3.10"
|
||||
- "3.11"
|
||||
- "3.12"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
|
@ -1,5 +1,9 @@
|
||||
# NetBox v4.0
|
||||
|
||||
## v4.0.1 (FUTURE)
|
||||
|
||||
---
|
||||
|
||||
## v4.0.0 (2024-05-06)
|
||||
|
||||
!!! tip "Plugin Maintainers"
|
||||
|
@ -48,7 +48,7 @@ class CircuitCircuitTerminationSerializer(WritableNestedSerializer):
|
||||
class CircuitSerializer(NetBoxModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuit-detail')
|
||||
provider = ProviderSerializer(nested=True)
|
||||
provider_account = ProviderAccountSerializer(nested=True, required=False, allow_null=True)
|
||||
provider_account = ProviderAccountSerializer(nested=True, required=False, allow_null=True, default=None)
|
||||
status = ChoiceField(choices=CircuitStatusChoices, required=False)
|
||||
type = CircuitTypeSerializer(nested=True)
|
||||
tenant = TenantSerializer(nested=True, required=False, allow_null=True)
|
||||
|
@ -45,6 +45,7 @@ class ProviderSerializer(NetBoxModelSerializer):
|
||||
class ProviderAccountSerializer(NetBoxModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:provideraccount-detail')
|
||||
provider = ProviderSerializer(nested=True)
|
||||
name = serializers.CharField(allow_blank=True, max_length=100, required=False, default='')
|
||||
|
||||
class Meta:
|
||||
model = ProviderAccount
|
||||
|
@ -141,7 +141,7 @@ class CircuitTest(APIViewTestCases.APIViewTestCase):
|
||||
{
|
||||
'cid': 'Circuit 6',
|
||||
'provider': providers[1].pk,
|
||||
'provider_account': provider_accounts[1].pk,
|
||||
# Omit provider account to test uniqueness constraint
|
||||
'type': circuit_types[1].pk,
|
||||
},
|
||||
]
|
||||
@ -237,7 +237,7 @@ class ProviderAccountTest(APIViewTestCases.APIViewTestCase):
|
||||
'account': '5678',
|
||||
},
|
||||
{
|
||||
'name': 'Provider Account 6',
|
||||
# Omit name to test uniqueness constraint
|
||||
'provider': providers[0].pk,
|
||||
'account': '6789',
|
||||
},
|
||||
|
@ -122,6 +122,7 @@ class DeviceWithConfigContextSerializer(DeviceSerializer):
|
||||
class VirtualDeviceContextSerializer(NetBoxModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:virtualdevicecontext-detail')
|
||||
device = DeviceSerializer(nested=True)
|
||||
identifier = serializers.IntegerField(allow_null=True, max_value=32767, min_value=0, required=False, default=None)
|
||||
tenant = TenantSerializer(nested=True, required=False, allow_null=True, default=None)
|
||||
primary_ip = IPAddressSerializer(nested=True, read_only=True, allow_null=True)
|
||||
primary_ip4 = IPAddressSerializer(nested=True, required=False, allow_null=True)
|
||||
|
@ -51,7 +51,7 @@ class SiteSerializer(NetBoxModelSerializer):
|
||||
status = ChoiceField(choices=SiteStatusChoices, required=False)
|
||||
region = RegionSerializer(nested=True, required=False, allow_null=True)
|
||||
group = SiteGroupSerializer(nested=True, required=False, allow_null=True)
|
||||
tenant = TenantSerializer(required=False, allow_null=True)
|
||||
tenant = TenantSerializer(nested=True, required=False, allow_null=True)
|
||||
time_zone = TimeZoneSerializerField(required=False, allow_null=True)
|
||||
asns = SerializedPKRelatedField(
|
||||
queryset=ASN.objects.all(),
|
||||
@ -83,7 +83,7 @@ class SiteSerializer(NetBoxModelSerializer):
|
||||
class LocationSerializer(NestedGroupModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:location-detail')
|
||||
site = SiteSerializer(nested=True)
|
||||
parent = NestedLocationSerializer(required=False, allow_null=True)
|
||||
parent = NestedLocationSerializer(required=False, allow_null=True, default=None)
|
||||
status = ChoiceField(choices=LocationStatusChoices, required=False)
|
||||
tenant = TenantSerializer(nested=True, required=False, allow_null=True)
|
||||
rack_count = serializers.IntegerField(read_only=True)
|
||||
|
@ -10,6 +10,7 @@ from dcim.models import *
|
||||
from extras.models import ConfigTemplate
|
||||
from ipam.models import ASN, RIR, VLAN, VRF
|
||||
from netbox.api.serializers import GenericObjectSerializer
|
||||
from tenancy.models import Tenant
|
||||
from utilities.testing import APITestCase, APIViewTestCases, create_test_device
|
||||
from virtualization.models import Cluster, ClusterType
|
||||
from wireless.choices import WirelessChannelChoices
|
||||
@ -152,6 +153,7 @@ class SiteTest(APIViewTestCases.APIViewTestCase):
|
||||
Site.objects.bulk_create(sites)
|
||||
|
||||
rir = RIR.objects.create(name='RFC 6996', is_private=True)
|
||||
tenant = Tenant.objects.create(name='Tenant 1', slug='tenant-1')
|
||||
|
||||
asns = [
|
||||
ASN(asn=65000 + i, rir=rir) for i in range(8)
|
||||
@ -166,6 +168,7 @@ class SiteTest(APIViewTestCases.APIViewTestCase):
|
||||
'group': groups[1].pk,
|
||||
'status': SiteStatusChoices.STATUS_ACTIVE,
|
||||
'asns': [asns[0].pk, asns[1].pk],
|
||||
'tenant': tenant.pk,
|
||||
},
|
||||
{
|
||||
'name': 'Site 5',
|
||||
@ -230,7 +233,7 @@ class LocationTest(APIViewTestCases.APIViewTestCase):
|
||||
'name': 'Test Location 6',
|
||||
'slug': 'test-location-6',
|
||||
'site': sites[1].pk,
|
||||
'parent': parent_locations[1].pk,
|
||||
# Omit parent to test uniqueness constraint
|
||||
'status': LocationStatusChoices.STATUS_PLANNED,
|
||||
},
|
||||
]
|
||||
@ -2307,6 +2310,6 @@ class VirtualDeviceContextTest(APIViewTestCases.APIViewTestCase):
|
||||
'device': devices[1].pk,
|
||||
'status': 'active',
|
||||
'name': 'VDC 3',
|
||||
'identifier': 3,
|
||||
# Omit identifier to test uniqueness constraint
|
||||
},
|
||||
]
|
||||
|
@ -781,6 +781,7 @@ class IPAddressView(generic.ObjectView):
|
||||
class IPAddressEditView(generic.ObjectEditView):
|
||||
queryset = IPAddress.objects.all()
|
||||
form = forms.IPAddressForm
|
||||
template_name = 'ipam/ipaddress_edit.html'
|
||||
|
||||
def alter_object(self, obj, request, url_args, url_kwargs):
|
||||
|
||||
|
@ -32,6 +32,7 @@ class MenuItem:
|
||||
link: str
|
||||
link_text: str
|
||||
permissions: Optional[Sequence[str]] = ()
|
||||
auth_required: Optional[bool] = False
|
||||
staff_only: Optional[bool] = False
|
||||
buttons: Optional[Sequence[MenuItemButton]] = ()
|
||||
|
||||
|
@ -371,6 +371,7 @@ ADMIN_MENU = Menu(
|
||||
MenuItem(
|
||||
link=f'users:user_list',
|
||||
link_text=_('Users'),
|
||||
auth_required=True,
|
||||
permissions=[f'auth.view_user'],
|
||||
buttons=(
|
||||
MenuItemButton(
|
||||
@ -390,6 +391,7 @@ ADMIN_MENU = Menu(
|
||||
MenuItem(
|
||||
link=f'users:group_list',
|
||||
link_text=_('Groups'),
|
||||
auth_required=True,
|
||||
permissions=[f'auth.view_group'],
|
||||
buttons=(
|
||||
MenuItemButton(
|
||||
@ -409,12 +411,14 @@ ADMIN_MENU = Menu(
|
||||
MenuItem(
|
||||
link=f'users:token_list',
|
||||
link_text=_('API Tokens'),
|
||||
auth_required=True,
|
||||
permissions=[f'users.view_token'],
|
||||
buttons=get_model_buttons('users', 'token')
|
||||
),
|
||||
MenuItem(
|
||||
link=f'users:objectpermission_list',
|
||||
link_text=_('Permissions'),
|
||||
auth_required=True,
|
||||
permissions=[f'users.view_objectpermission'],
|
||||
buttons=get_model_buttons('users', 'objectpermission', actions=['add'])
|
||||
),
|
||||
@ -425,16 +429,19 @@ ADMIN_MENU = Menu(
|
||||
items=(
|
||||
MenuItem(
|
||||
link='core:system',
|
||||
link_text=_('System')
|
||||
link_text=_('System'),
|
||||
auth_required=True
|
||||
),
|
||||
MenuItem(
|
||||
link='core:configrevision_list',
|
||||
link_text=_('Configuration History'),
|
||||
auth_required=True,
|
||||
permissions=['core.view_configrevision']
|
||||
),
|
||||
MenuItem(
|
||||
link='core:background_queue_list',
|
||||
link_text=_('Background Tasks')
|
||||
link_text=_('Background Tasks'),
|
||||
auth_required=True
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -25,7 +25,7 @@ from utilities.string import trailing_slash
|
||||
# Environment setup
|
||||
#
|
||||
|
||||
VERSION = '4.0.0'
|
||||
VERSION = '4.0.1-dev'
|
||||
HOSTNAME = platform.node()
|
||||
# Set the base directory two levels up
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
@ -522,7 +522,6 @@ if SENTRY_ENABLED:
|
||||
sentry_sdk.init(
|
||||
dsn=SENTRY_DSN,
|
||||
release=VERSION,
|
||||
integrations=[sentry_sdk.integrations.django.DjangoIntegration()],
|
||||
sample_rate=SENTRY_SAMPLE_RATE,
|
||||
traces_sample_rate=SENTRY_TRACES_SAMPLE_RATE,
|
||||
send_default_pii=True,
|
||||
|
@ -20,7 +20,7 @@
|
||||
{# Initialize color mode #}
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="{% static 'setmode.js' %}"
|
||||
src="{% static 'setmode.js' %}?v={{ settings.VERSION }}"
|
||||
onerror="window.location='{% url 'media_failure' %}?filename=setmode.js'">
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
|
@ -3,28 +3,19 @@
|
||||
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="nav-item">
|
||||
<a
|
||||
class="nav-link {% if active_tab == 'add' %}active{% endif %}"
|
||||
href="{% url 'ipam:ipaddress_add' %}{% querystring request %}"
|
||||
>
|
||||
{% if obj.pk %}{% trans "Edit" %}{% else %}{% trans "Create" %}{% endif %}
|
||||
<a href="{% url 'ipam:ipaddress_add' %}{% querystring request %}" class="nav-link {% if active_tab == 'add' %}active{% endif %}">
|
||||
{% if object.pk %}{% trans "Edit" %}{% else %}{% trans "Create" %}{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
{% if 'interface' in request.GET or 'vminterface' in request.GET %}
|
||||
<li class="nav-item">
|
||||
<a
|
||||
class="nav-link {% if active_tab == 'assign' %}active{% endif %}"
|
||||
href="{% url 'ipam:ipaddress_assign' %}{% querystring request %}"
|
||||
>
|
||||
<a href="{% url 'ipam:ipaddress_assign' %}{% querystring request %}" class="nav-link {% if active_tab == 'assign' %}active{% endif %}">
|
||||
{% trans "Assign IP" %}
|
||||
</a>
|
||||
</li>
|
||||
{% else %}
|
||||
{% elif not object.pk %}
|
||||
<li class="nav-item">
|
||||
<a
|
||||
class="nav-link {% if active_tab == 'bulk_add' %}active{% endif %}"
|
||||
href="{% url 'ipam:ipaddress_bulk_add' %}{% querystring request %}"
|
||||
>
|
||||
<a href="{% url 'ipam:ipaddress_bulk_add' %}{% querystring request %}" class="nav-link {% if active_tab == 'bulk_add' %}active{% endif %}">
|
||||
{% trans "Bulk Create" %}
|
||||
</a>
|
||||
</li>
|
||||
|
@ -17,21 +17,17 @@
|
||||
{% for field in form.hidden_fields %}
|
||||
{{ field }}
|
||||
{% endfor %}
|
||||
<div class="row mb-3">
|
||||
<div class="col col-md-8 offset-md-2">
|
||||
<div class="field-group">
|
||||
<h6>{% trans "Select IP Address" %}</h6>
|
||||
<div class="field-group my-5">
|
||||
<div class="row">
|
||||
<h5 class="col-9 offset-3">{% trans "Select IP Address" %}</h5>
|
||||
</div>
|
||||
{% render_field form.vrf_id %}
|
||||
{% render_field form.q %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col col-md-8 offset-md-2 text-end">
|
||||
<div class="text-end my-3">
|
||||
<a href="{{ return_url }}" class="btn btn-outline-secondary">{% trans "Cancel" %}</a>
|
||||
<button type="submit" class="btn btn-primary">{% trans "Search" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% if table %}
|
||||
<div class="row mb-3">
|
||||
|
5
netbox/templates/ipam/ipaddress_edit.html
Normal file
5
netbox/templates/ipam/ipaddress_edit.html
Normal file
@ -0,0 +1,5 @@
|
||||
{% extends 'generic/object_edit.html' %}
|
||||
|
||||
{% block tabs %}
|
||||
{% include 'ipam/inc/ipaddress_edit_header.html' with active_tab='add' %}
|
||||
{% endblock %}
|
@ -27,7 +27,7 @@ class TenantGroupSerializer(NestedGroupModelSerializer):
|
||||
|
||||
class TenantSerializer(NetBoxModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='tenancy-api:tenant-detail')
|
||||
group = TenantGroupSerializer(nested=True, required=False, allow_null=True)
|
||||
group = TenantGroupSerializer(nested=True, required=False, allow_null=True, default=None)
|
||||
|
||||
# Related object counts
|
||||
circuit_count = RelatedObjectCountField('circuits')
|
||||
|
@ -26,6 +26,8 @@ def nav(context):
|
||||
for group in menu.groups:
|
||||
items = []
|
||||
for item in group.items:
|
||||
if getattr(item, 'auth_required', False) and not user.is_authenticated:
|
||||
continue
|
||||
if not user.has_perms(item.permissions):
|
||||
continue
|
||||
if item.staff_only and not user.is_staff:
|
||||
|
@ -31,11 +31,11 @@ __all__ = (
|
||||
class VirtualMachineSerializer(NetBoxModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:virtualmachine-detail')
|
||||
status = ChoiceField(choices=VirtualMachineStatusChoices, required=False)
|
||||
site = SiteSerializer(nested=True, required=False, allow_null=True)
|
||||
cluster = ClusterSerializer(nested=True, required=False, allow_null=True)
|
||||
device = DeviceSerializer(nested=True, required=False, allow_null=True)
|
||||
site = SiteSerializer(nested=True, required=False, allow_null=True, default=None)
|
||||
cluster = ClusterSerializer(nested=True, required=False, allow_null=True, default=None)
|
||||
device = DeviceSerializer(nested=True, required=False, allow_null=True, default=None)
|
||||
role = DeviceRoleSerializer(nested=True, required=False, allow_null=True)
|
||||
tenant = TenantSerializer(nested=True, required=False, allow_null=True)
|
||||
tenant = TenantSerializer(nested=True, required=False, allow_null=True, default=None)
|
||||
platform = PlatformSerializer(nested=True, required=False, allow_null=True)
|
||||
primary_ip = IPAddressSerializer(nested=True, read_only=True, allow_null=True)
|
||||
primary_ip4 = IPAddressSerializer(nested=True, required=False, allow_null=True)
|
||||
@ -55,7 +55,6 @@ class VirtualMachineSerializer(NetBoxModelSerializer):
|
||||
'interface_count', 'virtual_disk_count',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'description')
|
||||
validators = []
|
||||
|
||||
|
||||
class VirtualMachineWithConfigContextSerializer(VirtualMachineSerializer):
|
||||
|
@ -1,4 +1,4 @@
|
||||
Django==5.0.5
|
||||
Django==5.0.6
|
||||
django-cors-headers==4.3.1
|
||||
django-debug-toolbar==4.3.0
|
||||
django-filter==24.2
|
||||
|
Loading…
Reference in New Issue
Block a user