1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Merge branch 'develop' into feature

This commit is contained in:
Jeremy Stretch
2023-08-15 11:04:03 -04:00
20 changed files with 801 additions and 61 deletions

View File

@ -14,7 +14,7 @@ body:
attributes: attributes:
label: NetBox version label: NetBox version
description: What version of NetBox are you currently running? description: What version of NetBox are you currently running?
placeholder: v3.5.7 placeholder: v3.5.8
validations: validations:
required: true required: true
- type: dropdown - type: dropdown

View File

@ -14,7 +14,7 @@ body:
attributes: attributes:
label: NetBox version label: NetBox version
description: What version of NetBox are you currently running? description: What version of NetBox are you currently running?
placeholder: v3.5.7 placeholder: v3.5.8
validations: validations:
required: true required: true
- type: dropdown - type: dropdown

View File

@ -0,0 +1,561 @@
{
"type": "object",
"additionalProperties": false,
"definitions": {
"airflow": {
"type": "string",
"enum": [
"front-to-rear",
"rear-to-front",
"left-to-right",
"right-to-left",
"side-to-rear",
"passive",
"mixed"
]
},
"weight-unit": {
"type": "string",
"enum": [
"kg",
"g",
"lb",
"oz"
]
},
"subdevice-role": {
"type": "string",
"enum": [
"parent",
"child"
]
},
"console-port": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"de-9",
"db-25",
"rj-11",
"rj-12",
"rj-45",
"mini-din-8",
"usb-a",
"usb-b",
"usb-c",
"usb-mini-a",
"usb-mini-b",
"usb-micro-a",
"usb-micro-b",
"usb-micro-ab",
"other"
]
}
}
},
"console-server-port": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"de-9",
"db-25",
"rj-11",
"rj-12",
"rj-45",
"mini-din-8",
"usb-a",
"usb-b",
"usb-c",
"usb-mini-a",
"usb-mini-b",
"usb-micro-a",
"usb-micro-b",
"usb-micro-ab",
"other"
]
}
}
},
"power-port": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"iec-60320-c6",
"iec-60320-c8",
"iec-60320-c14",
"iec-60320-c16",
"iec-60320-c20",
"iec-60320-c22",
"iec-60309-p-n-e-4h",
"iec-60309-p-n-e-6h",
"iec-60309-p-n-e-9h",
"iec-60309-2p-e-4h",
"iec-60309-2p-e-6h",
"iec-60309-2p-e-9h",
"iec-60309-3p-e-4h",
"iec-60309-3p-e-6h",
"iec-60309-3p-e-9h",
"iec-60309-3p-n-e-4h",
"iec-60309-3p-n-e-6h",
"iec-60309-3p-n-e-9h",
"iec-60906-1",
"nbr-14136-10a",
"nbr-14136-20a",
"nema-1-15p",
"nema-5-15p",
"nema-5-20p",
"nema-5-30p",
"nema-5-50p",
"nema-6-15p",
"nema-6-20p",
"nema-6-30p",
"nema-6-50p",
"nema-10-30p",
"nema-10-50p",
"nema-14-20p",
"nema-14-30p",
"nema-14-50p",
"nema-14-60p",
"nema-15-15p",
"nema-15-20p",
"nema-15-30p",
"nema-15-50p",
"nema-15-60p",
"nema-l1-15p",
"nema-l5-15p",
"nema-l5-20p",
"nema-l5-30p",
"nema-l5-50p",
"nema-l6-15p",
"nema-l6-20p",
"nema-l6-30p",
"nema-l6-50p",
"nema-l10-30p",
"nema-l14-20p",
"nema-l14-30p",
"nema-l14-50p",
"nema-l14-60p",
"nema-l15-20p",
"nema-l15-30p",
"nema-l15-50p",
"nema-l15-60p",
"nema-l21-20p",
"nema-l21-30p",
"nema-l22-30p",
"cs6361c",
"cs6365c",
"cs8165c",
"cs8265c",
"cs8365c",
"cs8465c",
"ita-c",
"ita-e",
"ita-f",
"ita-ef",
"ita-g",
"ita-h",
"ita-i",
"ita-j",
"ita-k",
"ita-l",
"ita-m",
"ita-n",
"ita-o",
"usb-a",
"usb-b",
"usb-c",
"usb-mini-a",
"usb-mini-b",
"usb-micro-a",
"usb-micro-b",
"usb-micro-ab",
"usb-3-b",
"usb-3-micro-b",
"dc-terminal",
"saf-d-grid",
"neutrik-powercon-20",
"neutrik-powercon-32",
"neutrik-powercon-true1",
"neutrik-powercon-true1-top",
"ubiquiti-smartpower",
"hardwired",
"other"
]
}
}
},
"power-outlet": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"iec-60320-c5",
"iec-60320-c7",
"iec-60320-c13",
"iec-60320-c15",
"iec-60320-c19",
"iec-60320-c21",
"iec-60309-p-n-e-4h",
"iec-60309-p-n-e-6h",
"iec-60309-p-n-e-9h",
"iec-60309-2p-e-4h",
"iec-60309-2p-e-6h",
"iec-60309-2p-e-9h",
"iec-60309-3p-e-4h",
"iec-60309-3p-e-6h",
"iec-60309-3p-e-9h",
"iec-60309-3p-n-e-4h",
"iec-60309-3p-n-e-6h",
"iec-60309-3p-n-e-9h",
"iec-60906-1",
"nbr-14136-10a",
"nbr-14136-20a",
"nema-1-15r",
"nema-5-15r",
"nema-5-20r",
"nema-5-30r",
"nema-5-50r",
"nema-6-15r",
"nema-6-20r",
"nema-6-30r",
"nema-6-50r",
"nema-10-30r",
"nema-10-50r",
"nema-14-20r",
"nema-14-30r",
"nema-14-50r",
"nema-14-60r",
"nema-15-15r",
"nema-15-20r",
"nema-15-30r",
"nema-15-50r",
"nema-15-60r",
"nema-l1-15r",
"nema-l5-15r",
"nema-l5-20r",
"nema-l5-30r",
"nema-l5-50r",
"nema-l6-15r",
"nema-l6-20r",
"nema-l6-30r",
"nema-l6-50r",
"nema-l10-30r",
"nema-l14-20r",
"nema-l14-30r",
"nema-l14-50r",
"nema-l14-60r",
"nema-l15-20r",
"nema-l15-30r",
"nema-l15-50r",
"nema-l15-60r",
"nema-l21-20r",
"nema-l21-30r",
"nema-l22-30r",
"CS6360C",
"CS6364C",
"CS8164C",
"CS8264C",
"CS8364C",
"CS8464C",
"ita-e",
"ita-f",
"ita-g",
"ita-h",
"ita-i",
"ita-j",
"ita-k",
"ita-l",
"ita-m",
"ita-n",
"ita-o",
"ita-multistandard",
"usb-a",
"usb-micro-b",
"usb-c",
"dc-terminal",
"hdot-cx",
"saf-d-grid",
"neutrik-powercon-20a",
"neutrik-powercon-32a",
"neutrik-powercon-true1",
"neutrik-powercon-true1-top",
"ubiquiti-smartpower",
"hardwired",
"other"
]
},
"feed-leg": {
"type": "string",
"enum": [
"A",
"B",
"C"
]
}
}
},
"interface": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"virtual",
"bridge",
"lag",
"100base-fx",
"100base-lfx",
"100base-tx",
"100base-t1",
"1000base-t",
"2.5gbase-t",
"5gbase-t",
"10gbase-t",
"10gbase-cx4",
"1000base-x-gbic",
"1000base-x-sfp",
"10gbase-x-sfpp",
"10gbase-x-xfp",
"10gbase-x-xenpak",
"10gbase-x-x2",
"25gbase-x-sfp28",
"50gbase-x-sfp56",
"40gbase-x-qsfpp",
"50gbase-x-sfp28",
"100gbase-x-cfp",
"100gbase-x-cfp2",
"200gbase-x-cfp2",
"100gbase-x-cfp4",
"100gbase-x-cxp",
"100gbase-x-cpak",
"100gbase-x-dsfp",
"100gbase-x-sfpdd",
"100gbase-x-qsfp28",
"100gbase-x-qsfpdd",
"200gbase-x-qsfp56",
"200gbase-x-qsfpdd",
"400gbase-x-qsfpdd",
"400gbase-x-osfp",
"400gbase-x-cdfp",
"400gbase-x-cfp8",
"800gbase-x-qsfpdd",
"800gbase-x-osfp",
"1000base-kx",
"10gbase-kr",
"10gbase-kx4",
"25gbase-kr",
"40gbase-kr4",
"50gbase-kr",
"100gbase-kp4",
"100gbase-kr2",
"100gbase-kr4",
"ieee802.11a",
"ieee802.11g",
"ieee802.11n",
"ieee802.11ac",
"ieee802.11ad",
"ieee802.11ax",
"ieee802.11ay",
"ieee802.15.1",
"other-wireless",
"gsm",
"cdma",
"lte",
"sonet-oc3",
"sonet-oc12",
"sonet-oc48",
"sonet-oc192",
"sonet-oc768",
"sonet-oc1920",
"sonet-oc3840",
"1gfc-sfp",
"2gfc-sfp",
"4gfc-sfp",
"8gfc-sfpp",
"16gfc-sfpp",
"32gfc-sfp28",
"64gfc-qsfpp",
"128gfc-qsfp28",
"infiniband-sdr",
"infiniband-ddr",
"infiniband-qdr",
"infiniband-fdr10",
"infiniband-fdr",
"infiniband-edr",
"infiniband-hdr",
"infiniband-ndr",
"infiniband-xdr",
"t1",
"e1",
"t3",
"e3",
"xdsl",
"docsis",
"gpon",
"xg-pon",
"xgs-pon",
"ng-pon2",
"epon",
"10g-epon",
"cisco-stackwise",
"cisco-stackwise-plus",
"cisco-flexstack",
"cisco-flexstack-plus",
"cisco-stackwise-80",
"cisco-stackwise-160",
"cisco-stackwise-320",
"cisco-stackwise-480",
"cisco-stackwise-1t",
"juniper-vcp",
"extreme-summitstack",
"extreme-summitstack-128",
"extreme-summitstack-256",
"extreme-summitstack-512",
"other"
]
},
"poe_mode": {
"type": "string",
"enum": [
"pd",
"pse"
]
},
"poe_type": {
"type": "string",
"enum": [
"type1-ieee802.3af",
"type2-ieee802.3at",
"type3-ieee802.3bt",
"type4-ieee802.3bt",
"passive-24v-2pair",
"passive-24v-4pair",
"passive-48v-2pair",
"passive-48v-4pair"
]
}
}
},
"front-port": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"8p8c",
"8p6c",
"8p4c",
"8p2c",
"6p6c",
"6p4c",
"6p2c",
"4p4c",
"4p2c",
"gg45",
"tera-4p",
"tera-2p",
"tera-1p",
"110-punch",
"bnc",
"f",
"n",
"mrj21",
"fc",
"lc",
"lc-pc",
"lc-upc",
"lc-apc",
"lsh",
"lsh-pc",
"lsh-upc",
"lsh-apc",
"lx5",
"lx5-pc",
"lx5-upc",
"lx5-apc",
"mpo",
"mtrj",
"sc",
"sc-pc",
"sc-upc",
"sc-apc",
"st",
"cs",
"sn",
"sma-905",
"sma-906",
"urm-p2",
"urm-p4",
"urm-p8",
"splice",
"other"
]
}
}
},
"rear-port": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"8p8c",
"8p6c",
"8p4c",
"8p2c",
"6p6c",
"6p4c",
"6p2c",
"4p4c",
"4p2c",
"gg45",
"tera-4p",
"tera-2p",
"tera-1p",
"110-punch",
"bnc",
"f",
"n",
"mrj21",
"fc",
"lc",
"lc-pc",
"lc-upc",
"lc-apc",
"lsh",
"lsh-pc",
"lsh-upc",
"lsh-apc",
"lx5",
"lx5-pc",
"lx5-upc",
"lx5-apc",
"mpo",
"mtrj",
"sc",
"sc-pc",
"sc-upc",
"sc-apc",
"st",
"cs",
"sn",
"sma-905",
"sma-906",
"urm-p2",
"urm-p4",
"urm-p8",
"splice",
"other"
]
}
}
}
}
}

View File

@ -70,6 +70,16 @@ Before each release, update each of NetBox's Python dependencies to its most rec
In cases where upgrading a dependency to its most recent release is breaking, it should be constrained to its current minor version in `base_requirements.txt` with an explanatory comment and revisited for the next major NetBox release (see the [Address Constrained Dependencies](#address-constrained-dependencies) section above). In cases where upgrading a dependency to its most recent release is breaking, it should be constrained to its current minor version in `base_requirements.txt` with an explanatory comment and revisited for the next major NetBox release (see the [Address Constrained Dependencies](#address-constrained-dependencies) section above).
### Rebuild the Device Type Definition Schema
Run the following command to update the device type definition validation schema:
```nohighlight
./manage.py buildschema --write
```
This will automatically update the schema file at `contrib/generated_schema.json`.
### Update Version and Changelog ### Update Version and Changelog
* Update the `VERSION` constant in `settings.py` to the new release version. * Update the `VERSION` constant in `settings.py` to the new release version.

View File

@ -1,23 +1,33 @@
# NetBox v3.5 # NetBox v3.5
## v3.5.8 (FUTURE) ## v3.5.9 (FUTURE)
---
## v3.5.8 (2023-08-15)
### Enhancements ### Enhancements
* [#10030](https://github.com/netbox-community/netbox/issues/10030) - Ship a validation schema for the device type library with each release
* [#11675](https://github.com/netbox-community/netbox/issues/11675) - Add support for specifying import/export route targets during VRF bulk import * [#11675](https://github.com/netbox-community/netbox/issues/11675) - Add support for specifying import/export route targets during VRF bulk import
* [#11922](https://github.com/netbox-community/netbox/issues/11922) - Automatically populate any VDC assignments from the parent when adding a child interface via the UI * [#11922](https://github.com/netbox-community/netbox/issues/11922) - Automatically populate any VDC assignments from the parent when adding a child interface via the UI
* [#12889](https://github.com/netbox-community/netbox/issues/12889) - Add 400GE CFP2 interface type * [#12889](https://github.com/netbox-community/netbox/issues/12889) - Add 400GE CFP2 interface type
* [#13033](https://github.com/netbox-community/netbox/issues/13033) - Add human-friendly speed column to interfaces table * [#13033](https://github.com/netbox-community/netbox/issues/13033) - Add human-friendly speed column to interfaces table
* [#13151](https://github.com/netbox-community/netbox/issues/13151) - Add "assigned" filter for IP addresses * [#13151](https://github.com/netbox-community/netbox/issues/13151) - Add "assigned" filter for IP addresses
* [#13368](https://github.com/netbox-community/netbox/issues/13368) - List installed plugins on the server error report page * [#13368](https://github.com/netbox-community/netbox/issues/13368) - List installed plugins on the server error report page
* [#13442](https://github.com/netbox-community/netbox/issues/13442) - Add 200 and 400 Gbps speeds to dropdown choices on interface form
### Bug Fixes ### Bug Fixes
* [#11578](https://github.com/netbox-community/netbox/issues/11578) - Fix schema definition for available IP & VLAN REST API endpoints
* [#12639](https://github.com/netbox-community/netbox/issues/12639) - Raise validation error for invalid alphanumeric ranges when creating objects
* [#12665](https://github.com/netbox-community/netbox/issues/12665) - Avoid escaping semicolons when rendering custom links * [#12665](https://github.com/netbox-community/netbox/issues/12665) - Avoid escaping semicolons when rendering custom links
* [#12750](https://github.com/netbox-community/netbox/issues/12750) - Automatically delete an AutoSyncRecord when its object is deleted * [#12750](https://github.com/netbox-community/netbox/issues/12750) - Automatically delete an AutoSyncRecord when its object is deleted
* [#13343](https://github.com/netbox-community/netbox/issues/13343) - Fix filtering of circuits under provider network view * [#13343](https://github.com/netbox-community/netbox/issues/13343) - Fix filtering of circuits under provider network view
* [#13369](https://github.com/netbox-community/netbox/issues/13369) - Fix job termination status for failed reports * [#13369](https://github.com/netbox-community/netbox/issues/13369) - Fix job termination status for failed reports
* [#13414](https://github.com/netbox-community/netbox/issues/13414) - Fix support for "hide-if-unset" custom fields on bulk import forms * [#13414](https://github.com/netbox-community/netbox/issues/13414) - Fix support for "hide-if-unset" custom fields on bulk import forms
* [#13446](https://github.com/netbox-community/netbox/issues/13446) - Don't disable bulk edit/delete buttons after deselecting "select all" checkbox
* [#13451](https://github.com/netbox-community/netbox/issues/13451) - Disable table ordering for custom link columns
--- ---

View File

@ -1143,6 +1143,8 @@ class InterfaceSpeedChoices(ChoiceSet):
(25000000, '25 Gbps'), (25000000, '25 Gbps'),
(40000000, '40 Gbps'), (40000000, '40 Gbps'),
(100000000, '100 Gbps'), (100000000, '100 Gbps'),
(200000000, '200 Gbps'),
(400000000, '400 Gbps'),
] ]

View File

@ -55,7 +55,10 @@ class ComponentCreateForm(forms.Form):
super().clean() super().clean()
# Validate that all replication fields generate an equal number of values # Validate that all replication fields generate an equal number of values
pattern_count = len(self.cleaned_data[self.replication_fields[0]]) if not (patterns := self.cleaned_data.get(self.replication_fields[0])):
return
pattern_count = len(patterns)
for field_name in self.replication_fields: for field_name in self.replication_fields:
value_count = len(self.cleaned_data[field_name]) value_count = len(self.cleaned_data[field_name])
if self.cleaned_data[field_name] and value_count != pattern_count: if self.cleaned_data[field_name] and value_count != pattern_count:

View File

@ -0,0 +1,62 @@
import json
import os
from django.conf import settings
from django.core.management.base import BaseCommand
from jinja2 import FileSystemLoader, Environment
from dcim.choices import *
TEMPLATE_FILENAME = 'devicetype_schema.jinja2'
OUTPUT_FILENAME = 'contrib/generated_schema.json'
CHOICES_MAP = {
'airflow_choices': DeviceAirflowChoices,
'weight_unit_choices': WeightUnitChoices,
'subdevice_role_choices': SubdeviceRoleChoices,
'console_port_type_choices': ConsolePortTypeChoices,
'console_server_port_type_choices': ConsolePortTypeChoices,
'power_port_type_choices': PowerPortTypeChoices,
'power_outlet_type_choices': PowerOutletTypeChoices,
'power_outlet_feedleg_choices': PowerOutletFeedLegChoices,
'interface_type_choices': InterfaceTypeChoices,
'interface_poe_mode_choices': InterfacePoEModeChoices,
'interface_poe_type_choices': InterfacePoETypeChoices,
'front_port_type_choices': PortTypeChoices,
'rear_port_type_choices': PortTypeChoices,
}
class Command(BaseCommand):
help = "Generate JSON schema for validating NetBox device type definitions"
def add_arguments(self, parser):
parser.add_argument(
'--write',
action='store_true',
help="Write the generated schema to file"
)
def handle(self, *args, **kwargs):
# Initialize template
template_loader = FileSystemLoader(searchpath=f'{settings.TEMPLATES_DIR}/extras/schema/')
template_env = Environment(loader=template_loader)
template = template_env.get_template(TEMPLATE_FILENAME)
# Render template
context = {
key: json.dumps(choices.values())
for key, choices in CHOICES_MAP.items()
}
rendered = template.render(**context)
if kwargs['write']:
# $root/contrib/generated_schema.json
filename = os.path.join(os.path.split(settings.BASE_DIR)[0], OUTPUT_FILENAME)
with open(filename, mode='w', encoding='UTF-8') as f:
f.write(json.dumps(json.loads(rendered), indent=4))
f.write('\n')
f.close()
self.stdout.write(self.style.SUCCESS(f"Schema written to {filename}."))
else:
self.stdout.write(rendered)

View File

@ -1,7 +1,5 @@
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
from django.db import transaction from django.db import transaction
from django.db.models import F
from django.db.models.functions import Round
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django_pglocks import advisory_lock from django_pglocks import advisory_lock
from drf_spectacular.utils import extend_schema from drf_spectacular.utils import extend_schema
@ -16,6 +14,7 @@ from circuits.models import Provider
from dcim.models import Site from dcim.models import Site
from ipam import filtersets from ipam import filtersets
from ipam.models import * from ipam.models import *
from ipam.models import L2VPN, L2VPNTermination
from ipam.utils import get_next_available_prefix from ipam.utils import get_next_available_prefix
from netbox.api.viewsets import NetBoxModelViewSet from netbox.api.viewsets import NetBoxModelViewSet
from netbox.api.viewsets.mixins import ObjectValidationMixin from netbox.api.viewsets.mixins import ObjectValidationMixin
@ -24,7 +23,6 @@ from netbox.constants import ADVISORY_LOCK_KEYS
from utilities.api import get_serializer_for_model from utilities.api import get_serializer_for_model
from utilities.utils import count_related from utilities.utils import count_related
from . import serializers from . import serializers
from ipam.models import L2VPN, L2VPNTermination
class IPAMRootView(APIRootView): class IPAMRootView(APIRootView):
@ -346,7 +344,11 @@ class AvailableASNsView(AvailableObjectsView):
def get(self, request, pk): def get(self, request, pk):
return super().get(request, pk) return super().get(request, pk)
@extend_schema(methods=["post"], responses={201: serializers.AvailableASNSerializer(many=True)}) @extend_schema(
methods=["post"],
responses={201: serializers.ASNSerializer(many=True)},
request=serializers.ASNSerializer(many=True),
)
def post(self, request, pk): def post(self, request, pk):
return super().post(request, pk) return super().post(request, pk)
@ -395,7 +397,11 @@ class AvailablePrefixesView(AvailableObjectsView):
def get(self, request, pk): def get(self, request, pk):
return super().get(request, pk) return super().get(request, pk)
@extend_schema(methods=["post"], responses={201: serializers.PrefixSerializer(many=True)}) @extend_schema(
methods=["post"],
responses={201: serializers.PrefixSerializer(many=True)},
request=serializers.PrefixSerializer(many=True),
)
def post(self, request, pk): def post(self, request, pk):
return super().post(request, pk) return super().post(request, pk)
@ -435,7 +441,11 @@ class AvailableIPAddressesView(AvailableObjectsView):
def get(self, request, pk): def get(self, request, pk):
return super().get(request, pk) return super().get(request, pk)
@extend_schema(methods=["post"], responses={201: serializers.IPAddressSerializer(many=True)}) @extend_schema(
methods=["post"],
responses={201: serializers.IPAddressSerializer(many=True)},
request=serializers.IPAddressSerializer(many=True),
)
def post(self, request, pk): def post(self, request, pk):
return super().post(request, pk) return super().post(request, pk)
@ -482,6 +492,10 @@ class AvailableVLANsView(AvailableObjectsView):
def get(self, request, pk): def get(self, request, pk):
return super().get(request, pk) return super().get(request, pk)
@extend_schema(methods=["post"], responses={201: serializers.VLANSerializer(many=True)}) @extend_schema(
methods=["post"],
responses={201: serializers.VLANSerializer(many=True)},
request=serializers.VLANSerializer(many=True),
)
def post(self, request, pk): def post(self, request, pk):
return super().post(request, pk) return super().post(request, pk)

View File

@ -256,7 +256,7 @@ class IPRangeFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
model = IPRange model = IPRange
fieldsets = ( fieldsets = (
(None, ('q', 'filter_id', 'tag')), (None, ('q', 'filter_id', 'tag')),
(_('Attriubtes'), ('family', 'vrf_id', 'status', 'role_id', 'mark_utilized')), (_('Attributes'), ('family', 'vrf_id', 'status', 'role_id', 'mark_utilized')),
(_('Tenant'), ('tenant_group_id', 'tenant_id')), (_('Tenant'), ('tenant_group_id', 'tenant_id')),
) )
family = forms.ChoiceField( family = forms.ChoiceField(

View File

@ -25,7 +25,7 @@ from netbox.constants import RQ_QUEUE_DEFAULT, RQ_QUEUE_HIGH, RQ_QUEUE_LOW
# Environment setup # Environment setup
# #
VERSION = '3.6-beta1' VERSION = '3.6-beta2'
# Hostname # Hostname
HOSTNAME = platform.node() HOSTNAME = platform.node()

View File

@ -511,9 +511,9 @@ class CustomLinkColumn(tables.Column):
""" """
def __init__(self, customlink, *args, **kwargs): def __init__(self, customlink, *args, **kwargs):
self.customlink = customlink self.customlink = customlink
kwargs['accessor'] = Accessor('pk') kwargs.setdefault('accessor', Accessor('pk'))
if 'verbose_name' not in kwargs: kwargs.setdefault('orderable', False)
kwargs['verbose_name'] = customlink.name kwargs.setdefault('verbose_name', customlink.name)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
import { getElement, getElements, findFirstAdjacent } from '../util'; import { getElements, findFirstAdjacent } from '../util';
/** /**
* If any PK checkbox is checked, uncheck the select all table checkbox and the select all * If any PK checkbox is checked, uncheck the select all table checkbox and the select all
@ -63,29 +63,6 @@ function handleSelectAllToggle(event: Event): void {
} }
} }
/**
* Synchronize the select all confirmation checkbox state with the select all confirmation button
* disabled state. If the select all confirmation checkbox is checked, the buttons should be
* enabled. If not, the buttons should be disabled.
*
* @param event Change Event
*/
function handleSelectAll(event: Event): void {
const target = event.currentTarget as HTMLInputElement;
const selectAllBox = getElement<HTMLDivElement>('select-all-box');
if (selectAllBox !== null) {
for (const button of selectAllBox.querySelectorAll<HTMLButtonElement>(
'button[type="submit"]',
)) {
if (target.checked) {
button.disabled = false;
} else {
button.disabled = true;
}
}
}
}
/** /**
* Initialize table select all elements. * Initialize table select all elements.
*/ */
@ -98,9 +75,4 @@ export function initSelectAll(): void {
for (const element of getElements<HTMLInputElement>('input[type="checkbox"][name="pk"]')) { for (const element of getElements<HTMLInputElement>('input[type="checkbox"][name="pk"]')) {
element.addEventListener('change', handlePkCheck); element.addEventListener('change', handlePkCheck);
} }
const selectAll = getElement<HTMLInputElement>('select-all');
if (selectAll !== null) {
selectAll.addEventListener('change', handleSelectAll);
}
} }

View File

@ -0,0 +1,4 @@
{% extends 'generic/object.html' %}
{% block tabs %}
{% endblock %}

View File

@ -0,0 +1,93 @@
{
"type": "object",
"additionalProperties": false,
"definitions": {
"airflow": {
"type": "string",
"enum": {{ airflow_choices }}
},
"weight-unit": {
"type": "string",
"enum": {{ weight_unit_choices }}
},
"subdevice-role": {
"type": "string",
"enum": {{ subdevice_role_choices }}
},
"console-port": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": {{ console_port_type_choices }}
}
}
},
"console-server-port": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": {{ console_server_port_type_choices }}
}
}
},
"power-port": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": {{ power_port_type_choices }}
}
}
},
"power-outlet": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": {{ power_outlet_type_choices }}
},
"feed-leg": {
"type": "string",
"enum": {{ power_outlet_feedleg_choices }}
}
}
},
"interface": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": {{ interface_type_choices }}
},
"poe_mode": {
"type": "string",
"enum": {{ interface_poe_mode_choices }}
},
"poe_type": {
"type": "string",
"enum": {{ interface_poe_type_choices }}
}
}
},
"front-port": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": {{ front_port_type_choices }}
}
}
},
"rear-port": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": {{ rear_port_type_choices}}
}
}
}
}
}

View File

@ -60,6 +60,9 @@ def parse_alphanumeric_range(string):
except ValueError: except ValueError:
begin, end = dash_range, dash_range begin, end = dash_range, dash_range
if begin.isdigit() and end.isdigit(): if begin.isdigit() and end.isdigit():
if int(begin) >= int(end):
raise forms.ValidationError(f'Range "{dash_range}" is invalid.')
for n in list(range(int(begin), int(end) + 1)): for n in list(range(int(begin), int(end) + 1)):
values.append(n) values.append(n)
else: else:
@ -71,6 +74,10 @@ def parse_alphanumeric_range(string):
# Not a valid range (more than a single character) # Not a valid range (more than a single character)
if not len(begin) == len(end) == 1: if not len(begin) == len(end) == 1:
raise forms.ValidationError(f'Range "{dash_range}" is invalid.') raise forms.ValidationError(f'Range "{dash_range}" is invalid.')
if ord(begin) >= ord(end):
raise forms.ValidationError(f'Range "{dash_range}" is invalid.')
for n in list(range(ord(begin), ord(end) + 1)): for n in list(range(ord(begin), ord(end) + 1)):
values.append(chr(n)) values.append(chr(n))
return values return values

View File

@ -264,8 +264,9 @@ class ExpandAlphanumeric(TestCase):
self.assertEqual(sorted(expand_alphanumeric_pattern('r[a-9]a')), []) self.assertEqual(sorted(expand_alphanumeric_pattern('r[a-9]a')), [])
def test_invalid_range_bounds(self): def test_invalid_range_bounds(self):
self.assertEqual(sorted(expand_alphanumeric_pattern('r[9-8]a')), []) with self.assertRaises(forms.ValidationError):
self.assertEqual(sorted(expand_alphanumeric_pattern('r[b-a]a')), []) sorted(expand_alphanumeric_pattern('r[9-8]a'))
sorted(expand_alphanumeric_pattern('r[b-a]a'))
def test_invalid_range_len(self): def test_invalid_range_len(self):
with self.assertRaises(forms.ValidationError): with self.assertRaises(forms.ValidationError):

View File

@ -1,6 +1,7 @@
bleach==6.0.0 bleach==6.0.0
Django==4.2.4
django-cors-headers==4.2.0 django-cors-headers==4.2.0
django-debug-toolbar==4.1.0 django-debug-toolbar==4.2.0
django-filter==23.2 django-filter==23.2
django-graphiql-debug-toolbar==0.2.0 django-graphiql-debug-toolbar==0.2.0
django-mptt==0.14 django-mptt==0.14
@ -14,7 +15,7 @@ django-taggit==4.0.0
django-timezone-field==5.1 django-timezone-field==5.1
djangorestframework==3.14.0 djangorestframework==3.14.0
drf-spectacular==0.26.4 drf-spectacular==0.26.4
drf-spectacular-sidecar==2023.7.1 drf-spectacular-sidecar==2023.8.1
feedparser==6.0.10 feedparser==6.0.10
graphene-django==3.0.0 graphene-django==3.0.0
gunicorn==21.2.0 gunicorn==21.2.0
@ -24,9 +25,9 @@ mkdocs-material==9.1.21
mkdocstrings[python-legacy]==0.22.0 mkdocstrings[python-legacy]==0.22.0
netaddr==0.8.0 netaddr==0.8.0
Pillow==10.0.0 Pillow==10.0.0
psycopg[binary,pool]==3.1.9 psycopg[binary,pool]==3.1.10
PyYAML==6.0.1 PyYAML==6.0.1
sentry-sdk==1.28.1 sentry-sdk==1.29.2
social-auth-app-django==5.2.0 social-auth-app-django==5.2.0
social-auth-core[openidconnect]==4.4.2 social-auth-core[openidconnect]==4.4.2
svgwrite==1.4.3 svgwrite==1.4.3