mirror of
https://github.com/netbox-community/netbox.git
synced 2026-01-11 14:22:16 -06:00
Compare commits
15 Commits
fix_module
...
3fea22dcd0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3fea22dcd0 | ||
|
|
c1ce532422 | ||
|
|
52745e39ee | ||
|
|
b12bb38031 | ||
|
|
16a9e5b924 | ||
|
|
97b9805b94 | ||
|
|
fe52e4cd74 | ||
|
|
2966288fb5 | ||
|
|
f5ec885c3a | ||
|
|
6cc182c7ce | ||
|
|
c1c912d84b | ||
|
|
7d945b24cb | ||
|
|
697fda1be3 | ||
|
|
144f23444b | ||
|
|
15c32d44e7 |
9472
contrib/openapi.json
9472
contrib/openapi.json
File diff suppressed because it is too large
Load Diff
@@ -10,14 +10,6 @@ Minor releases are published in April, August, and December of each calendar yea
|
||||
|
||||
This page contains a history of all major and minor releases since NetBox v2.0. For more detail on a specific patch release, please see the release notes page for that specific minor release.
|
||||
|
||||
#### [Version 4.5](./version-4.5.md) (January 2026)
|
||||
|
||||
* Lookup Modifiers in Filter Forms ([#7604](https://github.com/netbox-community/netbox/issues/7604))
|
||||
* Improved API Authentication Tokens ([#20210](https://github.com/netbox-community/netbox/issues/20210))
|
||||
* Object Ownership ([#20304](https://github.com/netbox-community/netbox/issues/20304))
|
||||
* Advanced Port Mappings ([#20564](https://github.com/netbox-community/netbox/issues/20564))
|
||||
* Cable Profiles ([#20788](https://github.com/netbox-community/netbox/issues/20788))
|
||||
|
||||
#### [Version 4.4](./version-4.4.md) (September 2025)
|
||||
|
||||
* Background Jobs for Bulk Operations ([#19589](https://github.com/netbox-community/netbox/issues/19589), [#19891](https://github.com/netbox-community/netbox/issues/19891))
|
||||
|
||||
@@ -1,150 +0,0 @@
|
||||
## v4.5.0 (FUTURE)
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* Python 3.10 and 3.11 are no longer supported. NetBox now requires Python 3.12, 3.13, or 3.14.
|
||||
* GraphQL API queries which filter by object IDs or enums must now specify a filter lookup similar to other fields. For example, `id: 123` becomes `id: {exact: 123 }`.
|
||||
* Rendering a device or virtual machine configuration is now restricted to users with the `render_config` permission for the applicable object type.
|
||||
* Retrieval of API token plaintexts is no longer supported. The `ALLOW_TOKEN_RETRIEVAL` config parameter has been removed.
|
||||
* API tokens can no longer be reassigned from one user to another.
|
||||
* A config context assigned to a platform will now also apply to any children of that platform. (Although this is typically desired behavior, it may introduce unanticipated changes for existing deployments.)
|
||||
* The `/api/dcim/cable-terminations/` REST API endpoint is now read-only. Cable terminations must be set on cables directly via the `/api/dcim/cables/` endpoint.
|
||||
* The UI view dedicated to swapping A/Z circuit terminations has been removed.
|
||||
* The experimental HTMX navigation feature has been removed.
|
||||
* The obsolete boolean field `is_staff` has been removed from the `User` model.
|
||||
* Removal of deprecated behavior
|
||||
* The `/api/extras/object-types/` REST API endpoint has been removed. (Use `/api/core/object-types/` instead.)
|
||||
* Webhooks no longer specify a `model` in payload data. (Reference `object_type` instead, which includes the parent app label.)
|
||||
* The obsolete module `core.models.contenttypes` has been removed (replaced in v4.4 by `core.models.object_types`).
|
||||
* The `load_yaml()` and `load_json()` utility methods have been removed from the base class for custom scripts.
|
||||
|
||||
### New Features
|
||||
|
||||
#### Lookup Modifiers in Filter Forms ([#7604](https://github.com/netbox-community/netbox/issues/7604))
|
||||
|
||||
Most object list filters within the UI have been extended to include optional lookup modifiers to support more complex queries. For instance, filters for numeric values now include a dropdown where a user can select "less than," "greater than," or "not" in addition to the default equivalency match. The specific modifiers available depend on the type of each filter. Plugins can register their own filtersets using the `register_filterset()` decorator to enable this new functionality.
|
||||
|
||||
(Note that this feature does not introduce any new filters. Rather, it makes available in the UI filters which already exist.)
|
||||
|
||||
#### Improved API Authentication Tokens ([#20210](https://github.com/netbox-community/netbox/issues/20210))
|
||||
|
||||
This release introduces a new version of API token (v2) which implements several security improvements. HMAC hashing with a cryptographic pepper is used to authenticate these tokens, obviating the need to store plaintexts. The new tokens also employ a non-sensitive key which can be shared to identify tokens without divulging their plaintexts. We've also adopted the standard "bearer" HTTP header format, as shown below.
|
||||
|
||||
```
|
||||
# v1 token header
|
||||
Authorization: Token <TOKEN>
|
||||
|
||||
# v2 token header
|
||||
Authorization: Bearer nbt_<KEY>.<TOKEN>
|
||||
```
|
||||
|
||||
Note that v2 token keys are prefixed with the fixed string `nbt_`, which can be used to aid in secret detection.
|
||||
|
||||
Backward compatibility with legacy (v1) tokens is retained in this release. However, users are strongly encouraged to begin using only v2 tokens, as support for legacy tokens will be removed in NetBox v4.7.
|
||||
|
||||
#### Object Ownership ([#20304](https://github.com/netbox-community/netbox/issues/20304))
|
||||
|
||||
An optional `owner` foreign key field has been added to most models. This enables the assignment of objects to a new Owner model, which represents a set of users and/or groups. Through this relationship, we can now convey ownership of objects within NetBox natively, without needing to rely on the assignment of tags or custom fields.
|
||||
|
||||
(Note that ownership differs significantly in function from tenancy. Ownership determines the parties responsible for the maintenance of an object, whereas as tenancy conveys an operational dependency.)
|
||||
|
||||
#### Advanced Port Mappings ([#20564](https://github.com/netbox-community/netbox/issues/20564))
|
||||
|
||||
The previous many-to-one mapping of front to rear ports has been expanded to support bidirectional mappings. The `rear_port` and `rear_port_position` fields on the FrontPort model have been replaced with an intermediary PortMapping model, which supports any number of assignments between front port/position pair and a rear port/position pair. This change unlocks the ability to model complex inline devices that swap individual fiber pairs between cables.
|
||||
|
||||
#### Cable Profiles ([#20788](https://github.com/netbox-community/netbox/issues/20788))
|
||||
|
||||
Cables can now be assigned profiles which determine how they are treated for path tracing. A profile indicates the number of discrete parallel channels or lanes carried by the cable among its endpoints. For example, a 1-to-4 breakout cable has four lanes, shared at one end via a common termination and split out at the other end to four separate terminations. Profiles, when assigned, enable NetBox to more accurately trace a specific connection within a cable, rather than the cable as a whole.
|
||||
|
||||
The assignment of cable profiles is optional: Cable tracing will continue to operate as before for cables with no profile assigned.
|
||||
|
||||
### Enhancements
|
||||
|
||||
* [#16681](https://github.com/netbox-community/netbox/issues/16681) - Introduce a `render_config` permission, which is now required to render a device or virtual machine configuration
|
||||
* [#18658](https://github.com/netbox-community/netbox/issues/18658) - Add a `start_on_boot` choice field for virtual machines
|
||||
* [#19095](https://github.com/netbox-community/netbox/issues/19095) - Add support for Python 3.13 and 3.14
|
||||
* [#19338](https://github.com/netbox-community/netbox/issues/19338) - Enable filter lookups for object IDs and enums in GraphQL API queries
|
||||
* [#19523](https://github.com/netbox-community/netbox/issues/19523) - Cache the number of instances for device, module, and rack types, and enable filtering by these counts
|
||||
* [#20417](https://github.com/netbox-community/netbox/issues/20417) - Add an optional `color` field for device type power outlets
|
||||
* [#20476](https://github.com/netbox-community/netbox/issues/20476) - Once provisioned, the owner of an API token cannot be changed
|
||||
* [#20492](https://github.com/netbox-community/netbox/issues/20492) - Completely disabled the means to retrieve legacy API token plaintexts (removed the `ALLOW_TOKEN_RETRIEVAL` config parameter)
|
||||
* [#20639](https://github.com/netbox-community/netbox/issues/20639) - Apply config contexts to devices/VMs assigned any child platform of the parent platform
|
||||
* [#20834](https://github.com/netbox-community/netbox/issues/20834) - Add an `enabled` boolean field to API tokens
|
||||
* [#20917](https://github.com/netbox-community/netbox/issues/20917) - Include usage reference on API token views
|
||||
* [#20925](https://github.com/netbox-community/netbox/issues/20925) - Add optional `comments` field to all subclasses of `OrganizationalModel`
|
||||
* [#20929](https://github.com/netbox-community/netbox/issues/20929) - Require the `render_config` permission to view a rendered device/VM configuration in the UI
|
||||
* [#20936](https://github.com/netbox-community/netbox/issues/20936) - Introduce the `/api/authentication-check/` REST API endpoint for validating authentication tokens
|
||||
* [#20959](https://github.com/netbox-community/netbox/issues/20959) - Include a count of related module types for a manufacturer in the REST API
|
||||
|
||||
### Plugins
|
||||
|
||||
* [#13182](https://github.com/netbox-community/netbox/issues/13182) - Added `PrimaryModel`, `OrganizationalModel`, and `NestedGroupModel` to the plugins API, as well as their respective base classes for various resources
|
||||
|
||||
### Other Changes
|
||||
|
||||
* [#16137](https://github.com/netbox-community/netbox/issues/16137) - Remove the obsolete boolean field `is_staff` from the `User` model
|
||||
* [#17571](https://github.com/netbox-community/netbox/issues/17571) - Remove the experimental HTMX navigation feature
|
||||
* [#17936](https://github.com/netbox-community/netbox/issues/17936) - Introduce a dedicated `GFKSerializerField` for representing generic foreign keys in API serializers
|
||||
* [#19889](https://github.com/netbox-community/netbox/issues/19889) - Drop support for Python 3.10 and 3.11
|
||||
* [#19898](https://github.com/netbox-community/netbox/issues/19898) - Remove the obsolete REST API endpoint `/api/extras/object-types/`
|
||||
* [#20088](https://github.com/netbox-community/netbox/issues/20088) - Remove the non-deterministic `model` key from webhook payload data
|
||||
* [#20095](https://github.com/netbox-community/netbox/issues/20095) - Remove the obsolete module `core.models.contenttypes`
|
||||
* [#20096](https://github.com/netbox-community/netbox/issues/20096) - Remove the `load_yaml()` and `load_json()` utility methods from the `BaseScript` class
|
||||
* [#20204](https://github.com/netbox-community/netbox/issues/20204) - Started migrating object views from custom HTML templates to declarative layouts
|
||||
* [#20295](https://github.com/netbox-community/netbox/issues/20295) - Cable terminations may be modified via the REST API only by modifying the cable itself
|
||||
* [#20617](https://github.com/netbox-community/netbox/issues/20617) - Introduce `BaseModel` as the global base class for models
|
||||
* [#20683](https://github.com/netbox-community/netbox/issues/20683) - Remove the UI view dedicated to swapping A/Z circuit terminations
|
||||
* [#20926](https://github.com/netbox-community/netbox/issues/20926) - Standardize naming of GraphQL filters
|
||||
|
||||
### REST API Changes
|
||||
|
||||
* Most objects now include an optional `owner` foreign key field.
|
||||
* The `/api/dcim/cable-terminations` endpoint is now read-only.
|
||||
* Introduced the `/api/authentication-check/` endpoint to test REST API credentials
|
||||
* `circuits.CircuitGroup`
|
||||
* Add optional `comments` field
|
||||
* `circuits.CircuitType`
|
||||
* Add optional `comments` field
|
||||
* `circuits.VirtualCircuitType`
|
||||
* Add optional `comments` field
|
||||
* `dcim.Cable`
|
||||
* Add the optional `profile` choice field
|
||||
* `dcim.FrontPort`
|
||||
* Removed the `rear_port` and `rear_port_position` fields
|
||||
* Add the `positions` integer field
|
||||
* Add the `rear_ports` list for port mappings
|
||||
* `dcim.InventoryItemRole`
|
||||
* Add optional `comments` field
|
||||
* `dcim.Manufacturer`
|
||||
* Add optional `comments` field
|
||||
* Add read-only `moduletype_count` integer field
|
||||
* `dcim.ModuleType`
|
||||
* Add read-only `module_count` integer field
|
||||
* `dcim.PowerOutletTemplate`
|
||||
* Add optional `color` field
|
||||
* `dcim.RackRole`
|
||||
* Add optional `comments` field
|
||||
* `dcim.RackType`
|
||||
* Add read-only `rack_count` integer field
|
||||
* `dcim.RearPort`
|
||||
* Add the `front_ports` list for port mappings
|
||||
* `ipam.ASNRange`
|
||||
* Add optional `comments` field
|
||||
* `ipam.RIR`
|
||||
* Add optional `comments` field
|
||||
* `ipam.Role`
|
||||
* Add optional `comments` field
|
||||
* `ipam.VLANGroup`
|
||||
* Add optional `comments` field
|
||||
* `tenancy.ContactRole`
|
||||
* Add optional `comments` field
|
||||
* `users.Token`
|
||||
* Add `enabled` boolean field
|
||||
* `virtualization.ClusterGroup`
|
||||
* Add optional `comments` field
|
||||
* `virtualization.ClusterType`
|
||||
* Add optional `comments` field
|
||||
* `virtualization.VirtualMachine`
|
||||
* Add optional `start_on_boot` choice field
|
||||
* `vpn.TunnelGroup`
|
||||
* Add optional `comments` field
|
||||
@@ -322,7 +322,6 @@ nav:
|
||||
- git Cheat Sheet: 'development/git-cheat-sheet.md'
|
||||
- Release Notes:
|
||||
- Summary: 'release-notes/index.md'
|
||||
- Version 4.5: 'release-notes/version-4.5.md'
|
||||
- Version 4.4: 'release-notes/version-4.4.md'
|
||||
- Version 4.3: 'release-notes/version-4.3.md'
|
||||
- Version 4.2: 'release-notes/version-4.2.md'
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('circuits', '0055_add_comments_to_organizationalmodel'),
|
||||
('contenttypes', '0002_remove_content_type_name'),
|
||||
('dcim', '0224_add_comments_to_organizationalmodel'),
|
||||
('extras', '0134_owner'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddIndex(
|
||||
model_name='circuittermination',
|
||||
index=models.Index(fields=['termination_type', 'termination_id'], name='circuits_ci_termina_505dda_idx'),
|
||||
),
|
||||
]
|
||||
@@ -335,9 +335,6 @@ class CircuitTermination(
|
||||
name='%(app_label)s_%(class)s_unique_circuit_term_side'
|
||||
),
|
||||
)
|
||||
indexes = (
|
||||
models.Index(fields=('termination_type', 'termination_id')),
|
||||
)
|
||||
verbose_name = _('circuit termination')
|
||||
verbose_name_plural = _('circuit terminations')
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ from ipam.models import VLAN
|
||||
from netbox.api.fields import ChoiceField, ContentTypeField, SerializedPKRelatedField
|
||||
from netbox.api.gfk_fields import GFKSerializerField
|
||||
from netbox.api.serializers import NetBoxModelSerializer
|
||||
from users.api.serializers_.mixins import OwnerMixin
|
||||
from vpn.api.serializers_.l2vpn import L2VPNTerminationSerializer
|
||||
from wireless.api.serializers_.nested import NestedWirelessLinkSerializer
|
||||
from wireless.api.serializers_.wirelesslans import WirelessLANSerializer
|
||||
@@ -41,12 +40,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class ConsoleServerPortSerializer(
|
||||
OwnerMixin,
|
||||
NetBoxModelSerializer,
|
||||
CabledObjectSerializer,
|
||||
ConnectedEndpointsSerializer
|
||||
):
|
||||
class ConsoleServerPortSerializer(NetBoxModelSerializer, CabledObjectSerializer, ConnectedEndpointsSerializer):
|
||||
device = DeviceSerializer(nested=True)
|
||||
module = ModuleSerializer(
|
||||
nested=True,
|
||||
@@ -70,18 +64,13 @@ class ConsoleServerPortSerializer(
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'label', 'type', 'speed', 'description',
|
||||
'mark_connected', 'cable', 'cable_end', 'link_peers', 'link_peers_type', 'connected_endpoints',
|
||||
'connected_endpoints_type', 'connected_endpoints_reachable', 'owner', 'tags', 'custom_fields', 'created',
|
||||
'connected_endpoints_type', 'connected_endpoints_reachable', 'tags', 'custom_fields', 'created',
|
||||
'last_updated', '_occupied',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', 'cable', '_occupied')
|
||||
|
||||
|
||||
class ConsolePortSerializer(
|
||||
OwnerMixin,
|
||||
NetBoxModelSerializer,
|
||||
CabledObjectSerializer,
|
||||
ConnectedEndpointsSerializer
|
||||
):
|
||||
class ConsolePortSerializer(NetBoxModelSerializer, CabledObjectSerializer, ConnectedEndpointsSerializer):
|
||||
device = DeviceSerializer(nested=True)
|
||||
module = ModuleSerializer(
|
||||
nested=True,
|
||||
@@ -105,18 +94,13 @@ class ConsolePortSerializer(
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'label', 'type', 'speed', 'description',
|
||||
'mark_connected', 'cable', 'cable_end', 'link_peers', 'link_peers_type', 'connected_endpoints',
|
||||
'connected_endpoints_type', 'connected_endpoints_reachable', 'owner', 'tags', 'custom_fields', 'created',
|
||||
'connected_endpoints_type', 'connected_endpoints_reachable', 'tags', 'custom_fields', 'created',
|
||||
'last_updated', '_occupied',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', 'cable', '_occupied')
|
||||
|
||||
|
||||
class PowerPortSerializer(
|
||||
OwnerMixin,
|
||||
NetBoxModelSerializer,
|
||||
CabledObjectSerializer,
|
||||
ConnectedEndpointsSerializer
|
||||
):
|
||||
class PowerPortSerializer(NetBoxModelSerializer, CabledObjectSerializer, ConnectedEndpointsSerializer):
|
||||
device = DeviceSerializer(nested=True)
|
||||
module = ModuleSerializer(
|
||||
nested=True,
|
||||
@@ -136,18 +120,13 @@ class PowerPortSerializer(
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'label', 'type', 'maximum_draw',
|
||||
'allocated_draw', 'description', 'mark_connected', 'cable', 'cable_end', 'link_peers', 'link_peers_type',
|
||||
'connected_endpoints', 'connected_endpoints_type', 'connected_endpoints_reachable', 'owner', 'tags',
|
||||
'custom_fields', 'created', 'last_updated', '_occupied',
|
||||
'connected_endpoints', 'connected_endpoints_type', 'connected_endpoints_reachable', 'tags', 'custom_fields',
|
||||
'created', 'last_updated', '_occupied',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', 'cable', '_occupied')
|
||||
|
||||
|
||||
class PowerOutletSerializer(
|
||||
OwnerMixin,
|
||||
NetBoxModelSerializer,
|
||||
CabledObjectSerializer,
|
||||
ConnectedEndpointsSerializer
|
||||
):
|
||||
class PowerOutletSerializer(NetBoxModelSerializer, CabledObjectSerializer, ConnectedEndpointsSerializer):
|
||||
device = DeviceSerializer(nested=True)
|
||||
module = ModuleSerializer(
|
||||
nested=True,
|
||||
@@ -180,17 +159,12 @@ class PowerOutletSerializer(
|
||||
'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'label', 'type', 'status', 'color',
|
||||
'power_port', 'feed_leg', 'description', 'mark_connected', 'cable', 'cable_end', 'link_peers',
|
||||
'link_peers_type', 'connected_endpoints', 'connected_endpoints_type', 'connected_endpoints_reachable',
|
||||
'owner', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
|
||||
'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', 'cable', '_occupied')
|
||||
|
||||
|
||||
class InterfaceSerializer(
|
||||
OwnerMixin,
|
||||
NetBoxModelSerializer,
|
||||
CabledObjectSerializer,
|
||||
ConnectedEndpointsSerializer
|
||||
):
|
||||
class InterfaceSerializer(NetBoxModelSerializer, CabledObjectSerializer, ConnectedEndpointsSerializer):
|
||||
device = DeviceSerializer(nested=True)
|
||||
vdcs = SerializedPKRelatedField(
|
||||
queryset=VirtualDeviceContext.objects.all(),
|
||||
@@ -252,7 +226,7 @@ class InterfaceSerializer(
|
||||
'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'untagged_vlan', 'tagged_vlans', 'qinq_svlan',
|
||||
'vlan_translation_policy', 'mark_connected', 'cable', 'cable_end', 'wireless_link', 'link_peers',
|
||||
'link_peers_type', 'wireless_lans', 'vrf', 'l2vpn_termination', 'connected_endpoints',
|
||||
'connected_endpoints_type', 'connected_endpoints_reachable', 'owner', 'tags', 'custom_fields', 'created',
|
||||
'connected_endpoints_type', 'connected_endpoints_reachable', 'tags', 'custom_fields', 'created',
|
||||
'last_updated', 'count_ipaddresses', 'count_fhrp_groups', '_occupied',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', 'cable', '_occupied')
|
||||
@@ -333,7 +307,7 @@ class RearPortMappingSerializer(serializers.ModelSerializer):
|
||||
fields = ('position', 'front_port', 'front_port_position')
|
||||
|
||||
|
||||
class RearPortSerializer(OwnerMixin, NetBoxModelSerializer, CabledObjectSerializer, PortSerializer):
|
||||
class RearPortSerializer(NetBoxModelSerializer, CabledObjectSerializer, PortSerializer):
|
||||
device = DeviceSerializer(nested=True)
|
||||
module = ModuleSerializer(
|
||||
nested=True,
|
||||
@@ -353,7 +327,7 @@ class RearPortSerializer(OwnerMixin, NetBoxModelSerializer, CabledObjectSerializ
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'label', 'type', 'color', 'positions',
|
||||
'front_ports', 'description', 'mark_connected', 'cable', 'cable_end', 'link_peers', 'link_peers_type',
|
||||
'owner', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
|
||||
'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', 'cable', '_occupied')
|
||||
|
||||
@@ -371,7 +345,7 @@ class FrontPortMappingSerializer(serializers.ModelSerializer):
|
||||
fields = ('position', 'rear_port', 'rear_port_position')
|
||||
|
||||
|
||||
class FrontPortSerializer(OwnerMixin, NetBoxModelSerializer, CabledObjectSerializer, PortSerializer):
|
||||
class FrontPortSerializer(NetBoxModelSerializer, CabledObjectSerializer, PortSerializer):
|
||||
device = DeviceSerializer(nested=True)
|
||||
module = ModuleSerializer(
|
||||
nested=True,
|
||||
@@ -391,12 +365,12 @@ class FrontPortSerializer(OwnerMixin, NetBoxModelSerializer, CabledObjectSeriali
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'label', 'type', 'color', 'positions',
|
||||
'rear_ports', 'description', 'mark_connected', 'cable', 'cable_end', 'link_peers', 'link_peers_type',
|
||||
'owner', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
|
||||
'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', 'cable', '_occupied')
|
||||
|
||||
|
||||
class ModuleBaySerializer(OwnerMixin, NetBoxModelSerializer):
|
||||
class ModuleBaySerializer(NetBoxModelSerializer):
|
||||
device = DeviceSerializer(nested=True)
|
||||
module = ModuleSerializer(
|
||||
nested=True,
|
||||
@@ -416,12 +390,12 @@ class ModuleBaySerializer(OwnerMixin, NetBoxModelSerializer):
|
||||
model = ModuleBay
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'installed_module', 'label', 'position',
|
||||
'description', 'owner', 'tags', 'custom_fields', 'created', 'last_updated',
|
||||
'description', 'tags', 'custom_fields', 'created', 'last_updated',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'installed_module', 'name', 'description')
|
||||
|
||||
|
||||
class DeviceBaySerializer(OwnerMixin, NetBoxModelSerializer):
|
||||
class DeviceBaySerializer(NetBoxModelSerializer):
|
||||
device = DeviceSerializer(nested=True)
|
||||
installed_device = DeviceSerializer(nested=True, required=False, allow_null=True)
|
||||
|
||||
@@ -429,12 +403,12 @@ class DeviceBaySerializer(OwnerMixin, NetBoxModelSerializer):
|
||||
model = DeviceBay
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'device', 'name', 'label', 'description', 'installed_device',
|
||||
'owner', 'tags', 'custom_fields', 'created', 'last_updated',
|
||||
'tags', 'custom_fields', 'created', 'last_updated',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'device', 'name', 'description')
|
||||
|
||||
|
||||
class InventoryItemSerializer(OwnerMixin, NetBoxModelSerializer):
|
||||
class InventoryItemSerializer(NetBoxModelSerializer):
|
||||
device = DeviceSerializer(nested=True)
|
||||
parent = serializers.PrimaryKeyRelatedField(queryset=InventoryItem.objects.all(), allow_null=True, default=None)
|
||||
role = InventoryItemRoleSerializer(nested=True, required=False, allow_null=True)
|
||||
@@ -453,6 +427,6 @@ class InventoryItemSerializer(OwnerMixin, NetBoxModelSerializer):
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'device', 'parent', 'name', 'label', 'status', 'role',
|
||||
'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered', 'description', 'component_type',
|
||||
'component_id', 'component', 'owner', 'tags', 'custom_fields', 'created', 'last_updated', '_depth',
|
||||
'component_id', 'component', 'tags', 'custom_fields', 'created', 'last_updated', '_depth',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', '_depth')
|
||||
|
||||
@@ -111,7 +111,7 @@ class DeviceWithConfigContextSerializer(DeviceSerializer):
|
||||
'id', 'url', 'display_url', 'display', 'name', 'device_type', 'role', 'tenant', 'platform', 'serial',
|
||||
'asset_tag', 'site', 'location', 'rack', 'position', 'face', 'latitude', 'longitude', 'parent_device',
|
||||
'status', 'airflow', 'primary_ip', 'primary_ip4', 'primary_ip6', 'oob_ip', 'cluster', 'virtual_chassis',
|
||||
'vc_position', 'vc_priority', 'description', 'owner', 'comments', 'config_template', 'config_context',
|
||||
'vc_position', 'vc_priority', 'description', 'comments', 'config_template', 'config_context',
|
||||
'local_context_data', 'tags', 'custom_fields', 'created', 'last_updated', 'console_port_count',
|
||||
'console_server_port_count', 'power_port_count', 'power_outlet_count', 'interface_count',
|
||||
'front_port_count', 'rear_port_count', 'device_bay_count', 'module_bay_count', 'inventory_item_count',
|
||||
|
||||
@@ -126,26 +126,18 @@ class ModuleCommonForm(forms.Form):
|
||||
_("Cannot install module with placeholder values in a module bay with no position defined.")
|
||||
)
|
||||
|
||||
token_count = template.name.count(MODULE_TOKEN)
|
||||
# Validate: depth must be >= token_count (can't expand tokens without context)
|
||||
if len(module_bays) < token_count:
|
||||
if len(module_bays) != template.name.count(MODULE_TOKEN):
|
||||
raise forms.ValidationError(
|
||||
_(
|
||||
"Cannot install module with placeholder values in a module bay tree {level} in tree "
|
||||
"but {tokens} placeholders given."
|
||||
).format(
|
||||
level=len(module_bays), tokens=token_count
|
||||
level=len(module_bays), tokens=template.name.count(MODULE_TOKEN)
|
||||
)
|
||||
)
|
||||
|
||||
if token_count == 1:
|
||||
# Single token: substitute with full path (e.g., "1/1" for depth 2)
|
||||
full_path = '/'.join([mb.position for mb in module_bays])
|
||||
resolved_name = resolved_name.replace(MODULE_TOKEN, full_path, 1)
|
||||
else:
|
||||
# Multiple tokens: substitute level-by-level (existing behavior)
|
||||
for mb in module_bays:
|
||||
resolved_name = resolved_name.replace(MODULE_TOKEN, mb.position, 1)
|
||||
for module_bay in module_bays:
|
||||
resolved_name = resolved_name.replace(MODULE_TOKEN, module_bay.position, 1)
|
||||
|
||||
existing_item = installed_components.get(resolved_name)
|
||||
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('contenttypes', '0002_remove_content_type_name'),
|
||||
('dcim', '0224_add_comments_to_organizationalmodel'),
|
||||
('extras', '0134_owner'),
|
||||
('users', '0015_owner'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddIndex(
|
||||
model_name='macaddress',
|
||||
index=models.Index(
|
||||
fields=['assigned_object_type', 'assigned_object_id'], name='dcim_macadd_assigne_54115d_idx'
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -175,16 +175,9 @@ class ModularComponentTemplateModel(ComponentTemplateModel):
|
||||
|
||||
if module:
|
||||
modules = self._get_module_tree(module)
|
||||
token_count = self.name.count(MODULE_TOKEN)
|
||||
name = self.name
|
||||
if token_count == 1:
|
||||
# Single token: substitute with full path (e.g., "1/1" for depth 2)
|
||||
full_path = '/'.join([m.module_bay.position for m in modules])
|
||||
name = name.replace(MODULE_TOKEN, full_path, 1)
|
||||
else:
|
||||
# Multiple tokens: substitute level-by-level (existing behavior)
|
||||
for m in modules:
|
||||
name = name.replace(MODULE_TOKEN, m.module_bay.position, 1)
|
||||
for module in modules:
|
||||
name = name.replace(MODULE_TOKEN, module.module_bay.position, 1)
|
||||
return name
|
||||
return self.name
|
||||
|
||||
@@ -194,16 +187,9 @@ class ModularComponentTemplateModel(ComponentTemplateModel):
|
||||
|
||||
if module:
|
||||
modules = self._get_module_tree(module)
|
||||
token_count = self.label.count(MODULE_TOKEN)
|
||||
label = self.label
|
||||
if token_count == 1:
|
||||
# Single token: substitute with full path (e.g., "1/1" for depth 2)
|
||||
full_path = '/'.join([m.module_bay.position for m in modules])
|
||||
label = label.replace(MODULE_TOKEN, full_path, 1)
|
||||
else:
|
||||
# Multiple tokens: substitute level-by-level (existing behavior)
|
||||
for m in modules:
|
||||
label = label.replace(MODULE_TOKEN, m.module_bay.position, 1)
|
||||
for module in modules:
|
||||
label = label.replace(MODULE_TOKEN, module.module_bay.position, 1)
|
||||
return label
|
||||
return self.label
|
||||
|
||||
|
||||
@@ -1318,10 +1318,7 @@ class MACAddress(PrimaryModel):
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ('mac_address', 'pk')
|
||||
indexes = (
|
||||
models.Index(fields=('assigned_object_type', 'assigned_object_id')),
|
||||
)
|
||||
ordering = ('mac_address', 'pk',)
|
||||
verbose_name = _('MAC address')
|
||||
verbose_name_plural = _('MAC addresses')
|
||||
|
||||
|
||||
@@ -848,720 +848,6 @@ class ModuleBayTestCase(TestCase):
|
||||
nested_bay = module.modulebays.get(name='SFP A-21')
|
||||
self.assertEqual(nested_bay.label, 'A-21')
|
||||
|
||||
def test_nested_module_single_placeholder_full_path(self):
|
||||
"""
|
||||
Test that installing a module at depth=2 with a single {module} placeholder
|
||||
in the interface template name resolves to the full path (e.g., "1/1").
|
||||
Regression test for transceiver modeling use case.
|
||||
"""
|
||||
manufacturer = Manufacturer.objects.first()
|
||||
site = Site.objects.first()
|
||||
device_role = DeviceRole.objects.first()
|
||||
|
||||
# Create device type with module bay template
|
||||
device_type = DeviceType.objects.create(
|
||||
manufacturer=manufacturer,
|
||||
model='Chassis Device',
|
||||
slug='chassis-device'
|
||||
)
|
||||
ModuleBayTemplate.objects.create(
|
||||
device_type=device_type,
|
||||
name='Line Card Bay 1',
|
||||
position='1'
|
||||
)
|
||||
|
||||
# Create line card module type with nested module bay
|
||||
line_card_type = ModuleType.objects.create(
|
||||
manufacturer=manufacturer,
|
||||
model='Line Card'
|
||||
)
|
||||
ModuleBayTemplate.objects.create(
|
||||
module_type=line_card_type,
|
||||
name='SFP Bay {module}/1',
|
||||
label='SFP {module}/1',
|
||||
position='1'
|
||||
)
|
||||
ModuleBayTemplate.objects.create(
|
||||
module_type=line_card_type,
|
||||
name='SFP Bay {module}/2',
|
||||
label='SFP {module}/2',
|
||||
position='2'
|
||||
)
|
||||
|
||||
# Create SFP module type with interface using single {module} placeholder
|
||||
sfp_type = ModuleType.objects.create(
|
||||
manufacturer=manufacturer,
|
||||
model='SFP Transceiver'
|
||||
)
|
||||
InterfaceTemplate.objects.create(
|
||||
module_type=sfp_type,
|
||||
name='SFP {module}',
|
||||
label='{module}',
|
||||
type=InterfaceTypeChoices.TYPE_10GE_SFP_PLUS
|
||||
)
|
||||
|
||||
# Create device
|
||||
device = Device.objects.create(
|
||||
name='Test Chassis',
|
||||
device_type=device_type,
|
||||
role=device_role,
|
||||
site=site
|
||||
)
|
||||
|
||||
# Install line card in bay 1
|
||||
line_card_bay = device.modulebays.get(name='Line Card Bay 1')
|
||||
line_card = Module.objects.create(
|
||||
device=device,
|
||||
module_bay=line_card_bay,
|
||||
module_type=line_card_type
|
||||
)
|
||||
|
||||
# Install SFP in nested bay 1 (depth=2)
|
||||
sfp_bay_1 = line_card.modulebays.get(name='SFP Bay 1/1')
|
||||
sfp_module_1 = Module.objects.create(
|
||||
device=device,
|
||||
module_bay=sfp_bay_1,
|
||||
module_type=sfp_type
|
||||
)
|
||||
|
||||
# Verify interface name resolves to full path "1/1"
|
||||
interface_1 = sfp_module_1.interfaces.first()
|
||||
self.assertEqual(interface_1.name, 'SFP 1/1')
|
||||
self.assertEqual(interface_1.label, '1/1')
|
||||
|
||||
# Install second SFP in nested bay 2 (depth=2) - verifies uniqueness
|
||||
sfp_bay_2 = line_card.modulebays.get(name='SFP Bay 1/2')
|
||||
sfp_module_2 = Module.objects.create(
|
||||
device=device,
|
||||
module_bay=sfp_bay_2,
|
||||
module_type=sfp_type
|
||||
)
|
||||
|
||||
# Verify second interface name resolves to full path "1/2"
|
||||
interface_2 = sfp_module_2.interfaces.first()
|
||||
self.assertEqual(interface_2.name, 'SFP 1/2')
|
||||
self.assertEqual(interface_2.label, '1/2')
|
||||
|
||||
def test_single_placeholder_direct_install_depth_1(self):
|
||||
"""
|
||||
Test that installing a module directly at depth=1 with a single {module}
|
||||
placeholder still resolves correctly (just the position, not a path).
|
||||
"""
|
||||
manufacturer = Manufacturer.objects.first()
|
||||
site = Site.objects.first()
|
||||
device_role = DeviceRole.objects.first()
|
||||
|
||||
# Create device type with module bay template
|
||||
device_type = DeviceType.objects.create(
|
||||
manufacturer=manufacturer,
|
||||
model='Simple Chassis',
|
||||
slug='simple-chassis'
|
||||
)
|
||||
ModuleBayTemplate.objects.create(
|
||||
device_type=device_type,
|
||||
name='SFP Bay 1',
|
||||
position='1'
|
||||
)
|
||||
|
||||
# Create SFP module type with interface using single {module} placeholder
|
||||
sfp_type = ModuleType.objects.create(
|
||||
manufacturer=manufacturer,
|
||||
model='Direct SFP'
|
||||
)
|
||||
InterfaceTemplate.objects.create(
|
||||
module_type=sfp_type,
|
||||
name='SFP {module}',
|
||||
label='{module}',
|
||||
type=InterfaceTypeChoices.TYPE_10GE_SFP_PLUS
|
||||
)
|
||||
|
||||
# Create device
|
||||
device = Device.objects.create(
|
||||
name='Test Simple Chassis',
|
||||
device_type=device_type,
|
||||
role=device_role,
|
||||
site=site
|
||||
)
|
||||
|
||||
# Install SFP directly in bay 1 (depth=1)
|
||||
sfp_bay = device.modulebays.get(name='SFP Bay 1')
|
||||
sfp_module = Module.objects.create(
|
||||
device=device,
|
||||
module_bay=sfp_bay,
|
||||
module_type=sfp_type
|
||||
)
|
||||
|
||||
# Verify interface name resolves to just "1"
|
||||
interface = sfp_module.interfaces.first()
|
||||
self.assertEqual(interface.name, 'SFP 1')
|
||||
self.assertEqual(interface.label, '1')
|
||||
|
||||
def test_multi_token_level_by_level_depth_2(self):
|
||||
"""
|
||||
T1: Multi-token behavior remains unchanged at depth=2.
|
||||
Ensure legacy {module}/{module} still resolves level-by-level.
|
||||
"""
|
||||
site = Site.objects.create(name='T1 Site', slug='t1-site')
|
||||
manufacturer = Manufacturer.objects.create(name='T1 Manufacturer', slug='t1-manufacturer')
|
||||
device_role = DeviceRole.objects.create(name='T1 Role', slug='t1-role')
|
||||
|
||||
# Create device type with module bay
|
||||
device_type = DeviceType.objects.create(
|
||||
manufacturer=manufacturer,
|
||||
model='T1 Chassis',
|
||||
slug='t1-chassis'
|
||||
)
|
||||
ModuleBayTemplate.objects.create(
|
||||
device_type=device_type,
|
||||
name='Bay 1',
|
||||
position='1'
|
||||
)
|
||||
|
||||
# Create line card module type with nested bay
|
||||
line_card_type = ModuleType.objects.create(
|
||||
manufacturer=manufacturer,
|
||||
model='T1 Line Card'
|
||||
)
|
||||
ModuleBayTemplate.objects.create(
|
||||
module_type=line_card_type,
|
||||
name='Nested Bay 2',
|
||||
position='2'
|
||||
)
|
||||
|
||||
# Create SFP module type with 2-token interface template
|
||||
sfp_type = ModuleType.objects.create(
|
||||
manufacturer=manufacturer,
|
||||
model='T1 SFP'
|
||||
)
|
||||
InterfaceTemplate.objects.create(
|
||||
module_type=sfp_type,
|
||||
name='SFP {module}/{module}',
|
||||
type=InterfaceTypeChoices.TYPE_10GE_SFP_PLUS
|
||||
)
|
||||
|
||||
# Create device and install modules
|
||||
device = Device.objects.create(
|
||||
name='T1 Device',
|
||||
device_type=device_type,
|
||||
role=device_role,
|
||||
site=site
|
||||
)
|
||||
|
||||
# Install line card at position 1
|
||||
line_card_bay = device.modulebays.get(name='Bay 1')
|
||||
line_card = Module.objects.create(
|
||||
device=device,
|
||||
module_bay=line_card_bay,
|
||||
module_type=line_card_type
|
||||
)
|
||||
|
||||
# Install SFP at nested bay (position 2)
|
||||
sfp_bay = line_card.modulebays.get(name='Nested Bay 2')
|
||||
sfp_module = Module.objects.create(
|
||||
device=device,
|
||||
module_bay=sfp_bay,
|
||||
module_type=sfp_type
|
||||
)
|
||||
|
||||
# Verify level-by-level substitution: 1/2 (not 1/2/1/2)
|
||||
interface = sfp_module.interfaces.first()
|
||||
self.assertEqual(interface.name, 'SFP 1/2')
|
||||
|
||||
def test_multi_token_deeper_tree_only_consumes_tokens(self):
|
||||
"""
|
||||
T2: Multi-token with deeper tree only consumes tokens (depth=3, tokens=2).
|
||||
2 tokens → 2 levels, even if tree is deeper.
|
||||
"""
|
||||
site = Site.objects.create(name='T2 Site', slug='t2-site')
|
||||
manufacturer = Manufacturer.objects.create(name='T2 Manufacturer', slug='t2-manufacturer')
|
||||
device_role = DeviceRole.objects.create(name='T2 Role', slug='t2-role')
|
||||
|
||||
# Create device type with module bay
|
||||
device_type = DeviceType.objects.create(
|
||||
manufacturer=manufacturer,
|
||||
model='T2 Chassis',
|
||||
slug='t2-chassis'
|
||||
)
|
||||
ModuleBayTemplate.objects.create(
|
||||
device_type=device_type,
|
||||
name='Bay 1',
|
||||
position='1'
|
||||
)
|
||||
|
||||
# Create level 2 module type with nested bay
|
||||
level2_type = ModuleType.objects.create(
|
||||
manufacturer=manufacturer,
|
||||
model='T2 Level2'
|
||||
)
|
||||
ModuleBayTemplate.objects.create(
|
||||
module_type=level2_type,
|
||||
name='Level2 Bay',
|
||||
position='1'
|
||||
)
|
||||
|
||||
# Create level 3 module type with nested bay
|
||||
level3_type = ModuleType.objects.create(
|
||||
manufacturer=manufacturer,
|
||||
model='T2 Level3'
|
||||
)
|
||||
ModuleBayTemplate.objects.create(
|
||||
module_type=level3_type,
|
||||
name='Level3 Bay',
|
||||
position='1'
|
||||
)
|
||||
|
||||
# Create leaf module type with 2-token interface template
|
||||
leaf_type = ModuleType.objects.create(
|
||||
manufacturer=manufacturer,
|
||||
model='T2 Leaf'
|
||||
)
|
||||
InterfaceTemplate.objects.create(
|
||||
module_type=leaf_type,
|
||||
name='SFP {module}/{module}',
|
||||
type=InterfaceTypeChoices.TYPE_10GE_SFP_PLUS
|
||||
)
|
||||
|
||||
# Create device and install 3 levels of modules
|
||||
device = Device.objects.create(
|
||||
name='T2 Device',
|
||||
device_type=device_type,
|
||||
role=device_role,
|
||||
site=site
|
||||
)
|
||||
|
||||
# Level 1
|
||||
bay1 = device.modulebays.get(name='Bay 1')
|
||||
module1 = Module.objects.create(
|
||||
device=device,
|
||||
module_bay=bay1,
|
||||
module_type=level2_type
|
||||
)
|
||||
|
||||
# Level 2
|
||||
bay2 = module1.modulebays.get(name='Level2 Bay')
|
||||
module2 = Module.objects.create(
|
||||
device=device,
|
||||
module_bay=bay2,
|
||||
module_type=level3_type
|
||||
)
|
||||
|
||||
# Level 3 (leaf)
|
||||
bay3 = module2.modulebays.get(name='Level3 Bay')
|
||||
leaf_module = Module.objects.create(
|
||||
device=device,
|
||||
module_bay=bay3,
|
||||
module_type=leaf_type
|
||||
)
|
||||
|
||||
# Verify: 2 tokens → consumes first 2 levels only: "1/1" (not "1/1/1")
|
||||
interface = leaf_module.interfaces.first()
|
||||
self.assertEqual(interface.name, 'SFP 1/1')
|
||||
|
||||
def test_too_many_tokens_fails_validation(self):
|
||||
"""
|
||||
T3: Too-many-tokens still fails (depth=2, tokens=3).
|
||||
Confirms the validation prevents impossible substitution.
|
||||
"""
|
||||
from dcim.forms import ModuleForm
|
||||
|
||||
site = Site.objects.create(name='T3 Site', slug='t3-site')
|
||||
manufacturer = Manufacturer.objects.create(name='T3 Manufacturer', slug='t3-manufacturer')
|
||||
device_role = DeviceRole.objects.create(name='T3 Role', slug='t3-role')
|
||||
|
||||
# Create device type with module bay
|
||||
device_type = DeviceType.objects.create(
|
||||
manufacturer=manufacturer,
|
||||
model='T3 Chassis',
|
||||
slug='t3-chassis'
|
||||
)
|
||||
ModuleBayTemplate.objects.create(
|
||||
device_type=device_type,
|
||||
name='Bay 1',
|
||||
position='1'
|
||||
)
|
||||
|
||||
# Create line card module type with nested bay
|
||||
line_card_type = ModuleType.objects.create(
|
||||
manufacturer=manufacturer,
|
||||
model='T3 Line Card'
|
||||
)
|
||||
ModuleBayTemplate.objects.create(
|
||||
module_type=line_card_type,
|
||||
name='Nested Bay',
|
||||
position='1'
|
||||
)
|
||||
|
||||
# Create leaf module type with 3-token interface template (too many!)
|
||||
leaf_type = ModuleType.objects.create(
|
||||
manufacturer=manufacturer,
|
||||
model='T3 Leaf'
|
||||
)
|
||||
InterfaceTemplate.objects.create(
|
||||
module_type=leaf_type,
|
||||
name='{module}/{module}/{module}',
|
||||
type=InterfaceTypeChoices.TYPE_10GE_SFP_PLUS
|
||||
)
|
||||
|
||||
# Create device and install line card
|
||||
device = Device.objects.create(
|
||||
name='T3 Device',
|
||||
device_type=device_type,
|
||||
role=device_role,
|
||||
site=site
|
||||
)
|
||||
|
||||
bay1 = device.modulebays.get(name='Bay 1')
|
||||
line_card = Module.objects.create(
|
||||
device=device,
|
||||
module_bay=bay1,
|
||||
module_type=line_card_type
|
||||
)
|
||||
|
||||
# Attempt to install leaf module at depth=2 with 3 tokens - should fail
|
||||
nested_bay = line_card.modulebays.get(name='Nested Bay')
|
||||
|
||||
form = ModuleForm(data={
|
||||
'device': device.pk,
|
||||
'module_bay': nested_bay.pk,
|
||||
'module_type': leaf_type.pk,
|
||||
'status': 'active',
|
||||
'replicate_components': True,
|
||||
'adopt_components': False,
|
||||
})
|
||||
|
||||
self.assertFalse(form.is_valid())
|
||||
# Check the error message mentions the mismatch
|
||||
self.assertIn('2', str(form.errors))
|
||||
self.assertIn('3', str(form.errors))
|
||||
|
||||
def test_label_substitution_matches_name_depth_2(self):
|
||||
"""
|
||||
T4: Label substitution works the same way as name (depth=2 single-token).
|
||||
"""
|
||||
site = Site.objects.create(name='T4 Site', slug='t4-site')
|
||||
manufacturer = Manufacturer.objects.create(name='T4 Manufacturer', slug='t4-manufacturer')
|
||||
device_role = DeviceRole.objects.create(name='T4 Role', slug='t4-role')
|
||||
|
||||
# Create device type with module bay
|
||||
device_type = DeviceType.objects.create(
|
||||
manufacturer=manufacturer,
|
||||
model='T4 Chassis',
|
||||
slug='t4-chassis'
|
||||
)
|
||||
ModuleBayTemplate.objects.create(
|
||||
device_type=device_type,
|
||||
name='Bay 1',
|
||||
position='1'
|
||||
)
|
||||
|
||||
# Create line card module type with nested bay at position 2
|
||||
line_card_type = ModuleType.objects.create(
|
||||
manufacturer=manufacturer,
|
||||
model='T4 Line Card'
|
||||
)
|
||||
ModuleBayTemplate.objects.create(
|
||||
module_type=line_card_type,
|
||||
name='Nested Bay',
|
||||
position='2'
|
||||
)
|
||||
|
||||
# Create leaf module type with single-token name AND label
|
||||
leaf_type = ModuleType.objects.create(
|
||||
manufacturer=manufacturer,
|
||||
model='T4 Leaf'
|
||||
)
|
||||
InterfaceTemplate.objects.create(
|
||||
module_type=leaf_type,
|
||||
name='SFP {module}',
|
||||
label='LBL {module}',
|
||||
type=InterfaceTypeChoices.TYPE_10GE_SFP_PLUS
|
||||
)
|
||||
|
||||
# Create device and install modules
|
||||
device = Device.objects.create(
|
||||
name='T4 Device',
|
||||
device_type=device_type,
|
||||
role=device_role,
|
||||
site=site
|
||||
)
|
||||
|
||||
bay1 = device.modulebays.get(name='Bay 1')
|
||||
line_card = Module.objects.create(
|
||||
device=device,
|
||||
module_bay=bay1,
|
||||
module_type=line_card_type
|
||||
)
|
||||
|
||||
nested_bay = line_card.modulebays.get(name='Nested Bay')
|
||||
leaf_module = Module.objects.create(
|
||||
device=device,
|
||||
module_bay=nested_bay,
|
||||
module_type=leaf_type
|
||||
)
|
||||
|
||||
# Verify both name and label resolve to full path
|
||||
interface = leaf_module.interfaces.first()
|
||||
self.assertEqual(interface.name, 'SFP 1/2')
|
||||
self.assertEqual(interface.label, 'LBL 1/2')
|
||||
|
||||
def test_non_interface_component_template_substitution(self):
|
||||
"""
|
||||
T5: Non-interface modular component templates (ConsolePortTemplate).
|
||||
Ensures the fix is general to all ModularComponentTemplateModel subclasses.
|
||||
"""
|
||||
site = Site.objects.create(name='T5 Site', slug='t5-site')
|
||||
manufacturer = Manufacturer.objects.create(name='T5 Manufacturer', slug='t5-manufacturer')
|
||||
device_role = DeviceRole.objects.create(name='T5 Role', slug='t5-role')
|
||||
|
||||
# Create device type with module bay
|
||||
device_type = DeviceType.objects.create(
|
||||
manufacturer=manufacturer,
|
||||
model='T5 Chassis',
|
||||
slug='t5-chassis'
|
||||
)
|
||||
ModuleBayTemplate.objects.create(
|
||||
device_type=device_type,
|
||||
name='Bay 1',
|
||||
position='1'
|
||||
)
|
||||
|
||||
# Create line card module type with nested bay at position 2
|
||||
line_card_type = ModuleType.objects.create(
|
||||
manufacturer=manufacturer,
|
||||
model='T5 Line Card'
|
||||
)
|
||||
ModuleBayTemplate.objects.create(
|
||||
module_type=line_card_type,
|
||||
name='Nested Bay',
|
||||
position='2'
|
||||
)
|
||||
|
||||
# Create leaf module type with ConsolePortTemplate using single token
|
||||
leaf_type = ModuleType.objects.create(
|
||||
manufacturer=manufacturer,
|
||||
model='T5 Leaf'
|
||||
)
|
||||
ConsolePortTemplate.objects.create(
|
||||
module_type=leaf_type,
|
||||
name='Console {module}',
|
||||
label='{module}'
|
||||
)
|
||||
|
||||
# Create device and install modules
|
||||
device = Device.objects.create(
|
||||
name='T5 Device',
|
||||
device_type=device_type,
|
||||
role=device_role,
|
||||
site=site
|
||||
)
|
||||
|
||||
bay1 = device.modulebays.get(name='Bay 1')
|
||||
line_card = Module.objects.create(
|
||||
device=device,
|
||||
module_bay=bay1,
|
||||
module_type=line_card_type
|
||||
)
|
||||
|
||||
nested_bay = line_card.modulebays.get(name='Nested Bay')
|
||||
leaf_module = Module.objects.create(
|
||||
device=device,
|
||||
module_bay=nested_bay,
|
||||
module_type=leaf_type
|
||||
)
|
||||
|
||||
# Verify ConsolePort resolves with full path
|
||||
console_port = leaf_module.consoleports.first()
|
||||
self.assertEqual(console_port.name, 'Console 1/2')
|
||||
self.assertEqual(console_port.label, '1/2')
|
||||
|
||||
def test_positions_with_slashes_join_correctly(self):
|
||||
"""
|
||||
T6: Positions that already contain slashes don't break joining (depth=2, single token).
|
||||
Some platforms use positions like 0/1 (PIC/port style) even before nesting.
|
||||
"""
|
||||
site = Site.objects.create(name='T6 Site', slug='t6-site')
|
||||
manufacturer = Manufacturer.objects.create(name='T6 Manufacturer', slug='t6-manufacturer')
|
||||
device_role = DeviceRole.objects.create(name='T6 Role', slug='t6-role')
|
||||
|
||||
# Create device type with module bay using slash in position
|
||||
device_type = DeviceType.objects.create(
|
||||
manufacturer=manufacturer,
|
||||
model='T6 Chassis',
|
||||
slug='t6-chassis'
|
||||
)
|
||||
ModuleBayTemplate.objects.create(
|
||||
device_type=device_type,
|
||||
name='PIC Bay',
|
||||
position='0/1' # Position already contains slash
|
||||
)
|
||||
|
||||
# Create line card module type with nested bay at position 2
|
||||
line_card_type = ModuleType.objects.create(
|
||||
manufacturer=manufacturer,
|
||||
model='T6 Line Card'
|
||||
)
|
||||
ModuleBayTemplate.objects.create(
|
||||
module_type=line_card_type,
|
||||
name='Nested Bay',
|
||||
position='2'
|
||||
)
|
||||
|
||||
# Create leaf module type with single-token interface template
|
||||
leaf_type = ModuleType.objects.create(
|
||||
manufacturer=manufacturer,
|
||||
model='T6 Leaf'
|
||||
)
|
||||
InterfaceTemplate.objects.create(
|
||||
module_type=leaf_type,
|
||||
name='Gi{module}',
|
||||
type=InterfaceTypeChoices.TYPE_1GE_FIXED
|
||||
)
|
||||
|
||||
# Create device and install modules
|
||||
device = Device.objects.create(
|
||||
name='T6 Device',
|
||||
device_type=device_type,
|
||||
role=device_role,
|
||||
site=site
|
||||
)
|
||||
|
||||
bay1 = device.modulebays.get(name='PIC Bay')
|
||||
line_card = Module.objects.create(
|
||||
device=device,
|
||||
module_bay=bay1,
|
||||
module_type=line_card_type
|
||||
)
|
||||
|
||||
nested_bay = line_card.modulebays.get(name='Nested Bay')
|
||||
leaf_module = Module.objects.create(
|
||||
device=device,
|
||||
module_bay=nested_bay,
|
||||
module_type=leaf_type
|
||||
)
|
||||
|
||||
# Verify: 0/1 + 2 = 0/1/2
|
||||
interface = leaf_module.interfaces.first()
|
||||
self.assertEqual(interface.name, 'Gi0/1/2')
|
||||
|
||||
def test_depth_1_single_token_no_extra_slashes(self):
|
||||
"""
|
||||
T7: Ensure depth=1 single-token still resolves to the position, not an unnecessary "path join".
|
||||
"""
|
||||
site = Site.objects.create(name='T7 Site', slug='t7-site')
|
||||
manufacturer = Manufacturer.objects.create(name='T7 Manufacturer', slug='t7-manufacturer')
|
||||
device_role = DeviceRole.objects.create(name='T7 Role', slug='t7-role')
|
||||
|
||||
# Create device type with module bay at position 7
|
||||
device_type = DeviceType.objects.create(
|
||||
manufacturer=manufacturer,
|
||||
model='T7 Chassis',
|
||||
slug='t7-chassis'
|
||||
)
|
||||
ModuleBayTemplate.objects.create(
|
||||
device_type=device_type,
|
||||
name='Bay 7',
|
||||
position='7'
|
||||
)
|
||||
|
||||
# Create module type with single-token template
|
||||
module_type = ModuleType.objects.create(
|
||||
manufacturer=manufacturer,
|
||||
model='T7 Module'
|
||||
)
|
||||
InterfaceTemplate.objects.create(
|
||||
module_type=module_type,
|
||||
name='{module}',
|
||||
type=InterfaceTypeChoices.TYPE_1GE_FIXED
|
||||
)
|
||||
|
||||
# Create device and install module directly at depth=1
|
||||
device = Device.objects.create(
|
||||
name='T7 Device',
|
||||
device_type=device_type,
|
||||
role=device_role,
|
||||
site=site
|
||||
)
|
||||
|
||||
bay = device.modulebays.get(name='Bay 7')
|
||||
module = Module.objects.create(
|
||||
device=device,
|
||||
module_bay=bay,
|
||||
module_type=module_type
|
||||
)
|
||||
|
||||
# Verify: just "7", not "7/" or similar
|
||||
interface = module.interfaces.first()
|
||||
self.assertEqual(interface.name, '7')
|
||||
|
||||
def test_multi_occurrence_tokens_level_by_level(self):
|
||||
"""
|
||||
T8: Multiple occurrences of {module} in a single template (token_count > 1) still level-by-level.
|
||||
Ensure the token_count logic and replacement loop behaves with duplicated patterns.
|
||||
"""
|
||||
site = Site.objects.create(name='T8 Site', slug='t8-site')
|
||||
manufacturer = Manufacturer.objects.create(name='T8 Manufacturer', slug='t8-manufacturer')
|
||||
device_role = DeviceRole.objects.create(name='T8 Role', slug='t8-role')
|
||||
|
||||
# Create device type with module bay
|
||||
device_type = DeviceType.objects.create(
|
||||
manufacturer=manufacturer,
|
||||
model='T8 Chassis',
|
||||
slug='t8-chassis'
|
||||
)
|
||||
ModuleBayTemplate.objects.create(
|
||||
device_type=device_type,
|
||||
name='Bay 1',
|
||||
position='1'
|
||||
)
|
||||
|
||||
# Create line card module type with nested bay at position 2
|
||||
line_card_type = ModuleType.objects.create(
|
||||
manufacturer=manufacturer,
|
||||
model='T8 Line Card'
|
||||
)
|
||||
ModuleBayTemplate.objects.create(
|
||||
module_type=line_card_type,
|
||||
name='Nested Bay',
|
||||
position='2'
|
||||
)
|
||||
|
||||
# Create leaf module type with 2-token template (non-slash separator)
|
||||
leaf_type = ModuleType.objects.create(
|
||||
manufacturer=manufacturer,
|
||||
model='T8 Leaf'
|
||||
)
|
||||
InterfaceTemplate.objects.create(
|
||||
module_type=leaf_type,
|
||||
name='X{module}-Y{module}',
|
||||
type=InterfaceTypeChoices.TYPE_1GE_FIXED
|
||||
)
|
||||
|
||||
# Create device and install modules
|
||||
device = Device.objects.create(
|
||||
name='T8 Device',
|
||||
device_type=device_type,
|
||||
role=device_role,
|
||||
site=site
|
||||
)
|
||||
|
||||
bay1 = device.modulebays.get(name='Bay 1')
|
||||
line_card = Module.objects.create(
|
||||
device=device,
|
||||
module_bay=bay1,
|
||||
module_type=line_card_type
|
||||
)
|
||||
|
||||
nested_bay = line_card.modulebays.get(name='Nested Bay')
|
||||
leaf_module = Module.objects.create(
|
||||
device=device,
|
||||
module_bay=nested_bay,
|
||||
module_type=leaf_type
|
||||
)
|
||||
|
||||
# Verify: X1-Y2 (level-by-level, not full-path stuffed into first)
|
||||
interface = leaf_module.interfaces.first()
|
||||
self.assertEqual(interface.name, 'X1-Y2')
|
||||
|
||||
|
||||
class CableTestCase(TestCase):
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ from core.models import ObjectType
|
||||
from dcim.choices import *
|
||||
from dcim.constants import *
|
||||
from dcim.models import *
|
||||
from extras.models import ConfigTemplate
|
||||
from ipam.models import ASN, RIR, VLAN, VRF
|
||||
from netbox.choices import CSVDelimiterChoices, ImportFormatChoices, WeightUnitChoices
|
||||
from tenancy.models import Tenant
|
||||
@@ -2340,28 +2339,6 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
url = reverse('dcim:device_inventory', kwargs={'pk': device.pk})
|
||||
self.assertHttpStatus(self.client.get(url), 200)
|
||||
|
||||
def test_device_renderconfig(self):
|
||||
configtemplate = ConfigTemplate.objects.create(
|
||||
name='Test Config Template',
|
||||
template_code='Config for device {{ device.name }}'
|
||||
)
|
||||
device = Device.objects.first()
|
||||
device.config_template = configtemplate
|
||||
device.save()
|
||||
url = reverse('dcim:device_render-config', kwargs={'pk': device.pk})
|
||||
|
||||
# User with only view permission should NOT be able to render config
|
||||
self.add_permissions('dcim.view_device')
|
||||
self.assertHttpStatus(self.client.get(url), 403)
|
||||
|
||||
# With render_config permission added should be able to render config
|
||||
self.add_permissions('dcim.render_config_device')
|
||||
self.assertHttpStatus(self.client.get(url), 200)
|
||||
|
||||
# With view permission removed should NOT be able to render config
|
||||
self.remove_permissions('dcim.view_device')
|
||||
self.assertHttpStatus(self.client.get(url), 403)
|
||||
|
||||
|
||||
class ModuleTestCase(
|
||||
# Module does not support bulk renaming (no name field) or
|
||||
|
||||
@@ -2682,7 +2682,6 @@ class DeviceConfigContextView(ObjectConfigContextView):
|
||||
class DeviceRenderConfigView(ObjectRenderConfigView):
|
||||
queryset = Device.objects.all()
|
||||
base_template = 'dcim/device/base.html'
|
||||
additional_permissions = ['dcim.render_config_device']
|
||||
tab = ViewTab(
|
||||
label=_('Render Config'),
|
||||
weight=2100,
|
||||
|
||||
@@ -51,14 +51,7 @@ class ImageAttachmentsPanel(panels.ObjectsTablePanel):
|
||||
]
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(
|
||||
'extras.imageattachment',
|
||||
filters={
|
||||
'object_type_id': lambda ctx: ContentType.objects.get_for_model(ctx['object']).pk,
|
||||
'object_id': lambda ctx: ctx['object'].pk,
|
||||
},
|
||||
**kwargs,
|
||||
)
|
||||
super().__init__('extras.imageattachment', **kwargs)
|
||||
|
||||
|
||||
class TagsPanel(panels.ObjectPanel):
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from dcim.models import Site
|
||||
from ipam.models import ASN, ASNRange, RIR
|
||||
from netbox.api.fields import RelatedObjectCountField, SerializedPKRelatedField
|
||||
from netbox.api.fields import RelatedObjectCountField
|
||||
from netbox.api.serializers import OrganizationalModelSerializer, PrimaryModelSerializer
|
||||
from tenancy.api.serializers_.tenants import TenantSerializer
|
||||
|
||||
__all__ = (
|
||||
'ASNRangeSerializer',
|
||||
'ASNSerializer',
|
||||
'ASNSiteSerializer',
|
||||
'AvailableASNSerializer',
|
||||
'RIRSerializer',
|
||||
)
|
||||
@@ -43,27 +41,9 @@ class ASNRangeSerializer(OrganizationalModelSerializer):
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'description')
|
||||
|
||||
|
||||
class ASNSiteSerializer(PrimaryModelSerializer):
|
||||
"""
|
||||
This serializer is meant for inclusion in ASNSerializer and is only used
|
||||
to avoid a circular import of SiteSerializer.
|
||||
"""
|
||||
class Meta:
|
||||
model = Site
|
||||
fields = ('id', 'url', 'display', 'name', 'description', 'slug')
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'description', 'slug')
|
||||
|
||||
|
||||
class ASNSerializer(PrimaryModelSerializer):
|
||||
rir = RIRSerializer(nested=True, required=False, allow_null=True)
|
||||
tenant = TenantSerializer(nested=True, required=False, allow_null=True)
|
||||
sites = SerializedPKRelatedField(
|
||||
queryset=Site.objects.all(),
|
||||
serializer=ASNSiteSerializer,
|
||||
nested=True,
|
||||
required=False,
|
||||
many=True
|
||||
)
|
||||
|
||||
# Related object counts
|
||||
site_count = RelatedObjectCountField('sites')
|
||||
@@ -73,7 +53,7 @@ class ASNSerializer(PrimaryModelSerializer):
|
||||
model = ASN
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'asn', 'rir', 'tenant', 'description', 'owner', 'comments', 'tags',
|
||||
'custom_fields', 'created', 'last_updated', 'site_count', 'provider_count', 'sites',
|
||||
'custom_fields', 'created', 'last_updated', 'site_count', 'provider_count',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'asn', 'description')
|
||||
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('contenttypes', '0002_remove_content_type_name'),
|
||||
('dcim', '0225_gfk_indexes'),
|
||||
('extras', '0134_owner'),
|
||||
('ipam', '0085_add_comments_to_organizationalmodel'),
|
||||
('tenancy', '0022_add_comments_to_organizationalmodel'),
|
||||
('users', '0015_owner'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddIndex(
|
||||
model_name='prefix',
|
||||
index=models.Index(fields=['scope_type', 'scope_id'], name='ipam_prefix_scope_t_fe84a6_idx'),
|
||||
),
|
||||
]
|
||||
@@ -282,10 +282,13 @@ class Prefix(ContactsMixin, GetAvailablePrefixesMixin, CachedScopeMixin, Primary
|
||||
ordering = (F('vrf').asc(nulls_first=True), 'prefix', 'pk') # (vrf, prefix) may be non-unique
|
||||
verbose_name = _('prefix')
|
||||
verbose_name_plural = _('prefixes')
|
||||
indexes = (
|
||||
models.Index(fields=('scope_type', 'scope_id')),
|
||||
GistIndex(fields=['prefix'], name='ipam_prefix_gist_idx', opclasses=['inet_ops']),
|
||||
)
|
||||
indexes = [
|
||||
GistIndex(
|
||||
fields=['prefix'],
|
||||
name='ipam_prefix_gist_idx',
|
||||
opclasses=['inet_ops'],
|
||||
),
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
10
netbox/project-static/dist/netbox.js
vendored
10
netbox/project-static/dist/netbox.js
vendored
File diff suppressed because one or more lines are too long
6
netbox/project-static/dist/netbox.js.map
vendored
6
netbox/project-static/dist/netbox.js.map
vendored
File diff suppressed because one or more lines are too long
@@ -28,10 +28,10 @@
|
||||
"bootstrap": "5.3.8",
|
||||
"clipboard": "2.0.11",
|
||||
"flatpickr": "4.6.13",
|
||||
"gridstack": "12.4.1",
|
||||
"gridstack": "12.3.3",
|
||||
"htmx.org": "2.0.8",
|
||||
"query-string": "9.3.1",
|
||||
"sass": "1.97.0",
|
||||
"sass": "1.95.0",
|
||||
"tom-select": "2.4.3",
|
||||
"typeface-inter": "3.18.1",
|
||||
"typeface-roboto-mono": "1.1.13"
|
||||
@@ -39,21 +39,21 @@
|
||||
"devDependencies": {
|
||||
"@eslint/compat": "^2.0.0",
|
||||
"@eslint/eslintrc": "^3.3.3",
|
||||
"@eslint/js": "^9.39.2",
|
||||
"@eslint/js": "^9.39.1",
|
||||
"@types/bootstrap": "5.2.10",
|
||||
"@types/cookie": "^1.0.0",
|
||||
"@types/node": "^24.10.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.48.1",
|
||||
"@typescript-eslint/parser": "^8.48.1",
|
||||
"esbuild": "^0.27.1",
|
||||
"esbuild": "^0.27.0",
|
||||
"esbuild-sass-plugin": "^3.3.1",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-import-resolver-typescript": "^4.4.4",
|
||||
"eslint-plugin-import": "^2.32.0",
|
||||
"eslint-plugin-prettier": "^5.5.1",
|
||||
"globals": "^16.5.0",
|
||||
"prettier": "^3.7.4",
|
||||
"prettier": "^3.7.3",
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"resolutions": {
|
||||
|
||||
@@ -24,135 +24,135 @@
|
||||
dependencies:
|
||||
tslib "^2.4.0"
|
||||
|
||||
"@esbuild/aix-ppc64@0.27.1":
|
||||
version "0.27.1"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.1.tgz#116edcd62c639ed8ab551e57b38251bb28384de4"
|
||||
integrity sha512-HHB50pdsBX6k47S4u5g/CaLjqS3qwaOVE5ILsq64jyzgMhLuCuZ8rGzM9yhsAjfjkbgUPMzZEPa7DAp7yz6vuA==
|
||||
"@esbuild/aix-ppc64@0.27.0":
|
||||
version "0.27.0"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.0.tgz#1d8be43489a961615d49e037f1bfa0f52a773737"
|
||||
integrity sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A==
|
||||
|
||||
"@esbuild/android-arm64@0.27.1":
|
||||
version "0.27.1"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.27.1.tgz#31c00d864c80f6de1900a11de8a506dbfbb27349"
|
||||
integrity sha512-45fuKmAJpxnQWixOGCrS+ro4Uvb4Re9+UTieUY2f8AEc+t7d4AaZ6eUJ3Hva7dtrxAAWHtlEFsXFMAgNnGU9uQ==
|
||||
"@esbuild/android-arm64@0.27.0":
|
||||
version "0.27.0"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.27.0.tgz#bd1763194aad60753fa3338b1ba9bda974b58724"
|
||||
integrity sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ==
|
||||
|
||||
"@esbuild/android-arm@0.27.1":
|
||||
version "0.27.1"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.27.1.tgz#d2b73ab0ba894923a1d1378fd4b15cc20985f436"
|
||||
integrity sha512-kFqa6/UcaTbGm/NncN9kzVOODjhZW8e+FRdSeypWe6j33gzclHtwlANs26JrupOntlcWmB0u8+8HZo8s7thHvg==
|
||||
"@esbuild/android-arm@0.27.0":
|
||||
version "0.27.0"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.27.0.tgz#69c7b57f02d3b3618a5ba4f82d127b57665dc397"
|
||||
integrity sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ==
|
||||
|
||||
"@esbuild/android-x64@0.27.1":
|
||||
version "0.27.1"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.27.1.tgz#d9f74d8278191317250cfe0c15a13f410540b122"
|
||||
integrity sha512-LBEpOz0BsgMEeHgenf5aqmn/lLNTFXVfoWMUox8CtWWYK9X4jmQzWjoGoNb8lmAYml/tQ/Ysvm8q7szu7BoxRQ==
|
||||
"@esbuild/android-x64@0.27.0":
|
||||
version "0.27.0"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.27.0.tgz#6ea22b5843acb23243d0126c052d7d3b6a11ca90"
|
||||
integrity sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q==
|
||||
|
||||
"@esbuild/darwin-arm64@0.27.1":
|
||||
version "0.27.1"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.27.1.tgz#baf6914b8c57ed9d41f9de54023aa3ff9b084680"
|
||||
integrity sha512-veg7fL8eMSCVKL7IW4pxb54QERtedFDfY/ASrumK/SbFsXnRazxY4YykN/THYqFnFwJ0aVjiUrVG2PwcdAEqQQ==
|
||||
"@esbuild/darwin-arm64@0.27.0":
|
||||
version "0.27.0"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.27.0.tgz#5ad7c02bc1b1a937a420f919afe40665ba14ad1e"
|
||||
integrity sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg==
|
||||
|
||||
"@esbuild/darwin-x64@0.27.1":
|
||||
version "0.27.1"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.27.1.tgz#64e37400795f780a76c858a118ff19681a64b4e0"
|
||||
integrity sha512-+3ELd+nTzhfWb07Vol7EZ+5PTbJ/u74nC6iv4/lwIU99Ip5uuY6QoIf0Hn4m2HoV0qcnRivN3KSqc+FyCHjoVQ==
|
||||
"@esbuild/darwin-x64@0.27.0":
|
||||
version "0.27.0"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.27.0.tgz#48470c83c5fd6d1fc7c823c2c603aeee96e101c9"
|
||||
integrity sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g==
|
||||
|
||||
"@esbuild/freebsd-arm64@0.27.1":
|
||||
version "0.27.1"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.1.tgz#6572f2f235933eee906e070dfaae54488ee60acd"
|
||||
integrity sha512-/8Rfgns4XD9XOSXlzUDepG8PX+AVWHliYlUkFI3K3GB6tqbdjYqdhcb4BKRd7C0BhZSoaCxhv8kTcBrcZWP+xg==
|
||||
"@esbuild/freebsd-arm64@0.27.0":
|
||||
version "0.27.0"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.0.tgz#d5a8effd8b0be7be613cd1009da34d629d4c2457"
|
||||
integrity sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw==
|
||||
|
||||
"@esbuild/freebsd-x64@0.27.1":
|
||||
version "0.27.1"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.27.1.tgz#83105dba9cf6ac4f44336799446d7f75c8c3a1e1"
|
||||
integrity sha512-GITpD8dK9C+r+5yRT/UKVT36h/DQLOHdwGVwwoHidlnA168oD3uxA878XloXebK4Ul3gDBBIvEdL7go9gCUFzQ==
|
||||
"@esbuild/freebsd-x64@0.27.0":
|
||||
version "0.27.0"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.27.0.tgz#9bde638bda31aa244d6d64dbafafb41e6e799bcc"
|
||||
integrity sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g==
|
||||
|
||||
"@esbuild/linux-arm64@0.27.1":
|
||||
version "0.27.1"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.27.1.tgz#035ff647d4498bdf16eb2d82801f73b366477dfa"
|
||||
integrity sha512-W9//kCrh/6in9rWIBdKaMtuTTzNj6jSeG/haWBADqLLa9P8O5YSRDzgD5y9QBok4AYlzS6ARHifAb75V6G670Q==
|
||||
"@esbuild/linux-arm64@0.27.0":
|
||||
version "0.27.0"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.27.0.tgz#96008c3a207d8ca495708db714c475ea5bf7e2af"
|
||||
integrity sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ==
|
||||
|
||||
"@esbuild/linux-arm@0.27.1":
|
||||
version "0.27.1"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.27.1.tgz#3516c74d2afbe305582dbb546d60f7978a8ece7f"
|
||||
integrity sha512-ieMID0JRZY/ZeCrsFQ3Y3NlHNCqIhTprJfDgSB3/lv5jJZ8FX3hqPyXWhe+gvS5ARMBJ242PM+VNz/ctNj//eA==
|
||||
"@esbuild/linux-arm@0.27.0":
|
||||
version "0.27.0"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.27.0.tgz#9b47cb0f222e567af316e978c7f35307db97bc0e"
|
||||
integrity sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ==
|
||||
|
||||
"@esbuild/linux-ia32@0.27.1":
|
||||
version "0.27.1"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.27.1.tgz#788db5db8ecd3d75dd41c42de0fe8f1fd967a4a7"
|
||||
integrity sha512-VIUV4z8GD8rtSVMfAj1aXFahsi/+tcoXXNYmXgzISL+KB381vbSTNdeZHHHIYqFyXcoEhu9n5cT+05tRv13rlw==
|
||||
"@esbuild/linux-ia32@0.27.0":
|
||||
version "0.27.0"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.27.0.tgz#d1e1e38d406cbdfb8a49f4eca0c25bbc344e18cc"
|
||||
integrity sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw==
|
||||
|
||||
"@esbuild/linux-loong64@0.27.1":
|
||||
version "0.27.1"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.27.1.tgz#8211f08b146916a6302ec2b8f87ec0cc4b62c49e"
|
||||
integrity sha512-l4rfiiJRN7sTNI//ff65zJ9z8U+k6zcCg0LALU5iEWzY+a1mVZ8iWC1k5EsNKThZ7XCQ6YWtsZ8EWYm7r1UEsg==
|
||||
"@esbuild/linux-loong64@0.27.0":
|
||||
version "0.27.0"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.27.0.tgz#c13bc6a53e3b69b76f248065bebee8415b44dfce"
|
||||
integrity sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg==
|
||||
|
||||
"@esbuild/linux-mips64el@0.27.1":
|
||||
version "0.27.1"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.27.1.tgz#cc58586ea83b3f171e727a624e7883a1c3eb4c04"
|
||||
integrity sha512-U0bEuAOLvO/DWFdygTHWY8C067FXz+UbzKgxYhXC0fDieFa0kDIra1FAhsAARRJbvEyso8aAqvPdNxzWuStBnA==
|
||||
"@esbuild/linux-mips64el@0.27.0":
|
||||
version "0.27.0"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.27.0.tgz#05f8322eb0a96ce1bfbc59691abe788f71e2d217"
|
||||
integrity sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg==
|
||||
|
||||
"@esbuild/linux-ppc64@0.27.1":
|
||||
version "0.27.1"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.27.1.tgz#632477bbd98175cf8e53a7c9952d17fb2d6d4115"
|
||||
integrity sha512-NzdQ/Xwu6vPSf/GkdmRNsOfIeSGnh7muundsWItmBsVpMoNPVpM61qNzAVY3pZ1glzzAxLR40UyYM23eaDDbYQ==
|
||||
"@esbuild/linux-ppc64@0.27.0":
|
||||
version "0.27.0"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.27.0.tgz#6fc5e7af98b4fb0c6a7f0b73ba837ce44dc54980"
|
||||
integrity sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA==
|
||||
|
||||
"@esbuild/linux-riscv64@0.27.1":
|
||||
version "0.27.1"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.27.1.tgz#35435a82435a8a750edf433b83ac0d10239ac3fe"
|
||||
integrity sha512-7zlw8p3IApcsN7mFw0O1Z1PyEk6PlKMu18roImfl3iQHTnr/yAfYv6s4hXPidbDoI2Q0pW+5xeoM4eTCC0UdrQ==
|
||||
"@esbuild/linux-riscv64@0.27.0":
|
||||
version "0.27.0"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.27.0.tgz#508afa9f69a3f97368c0bf07dd894a04af39d86e"
|
||||
integrity sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ==
|
||||
|
||||
"@esbuild/linux-s390x@0.27.1":
|
||||
version "0.27.1"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.27.1.tgz#172edd7086438edacd86c0e2ea25ac9dbb62aac5"
|
||||
integrity sha512-cGj5wli+G+nkVQdZo3+7FDKC25Uh4ZVwOAK6A06Hsvgr8WqBBuOy/1s+PUEd/6Je+vjfm6stX0kmib5b/O2Ykw==
|
||||
"@esbuild/linux-s390x@0.27.0":
|
||||
version "0.27.0"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.27.0.tgz#21fda656110ee242fc64f87a9e0b0276d4e4ec5b"
|
||||
integrity sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w==
|
||||
|
||||
"@esbuild/linux-x64@0.27.1":
|
||||
version "0.27.1"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.27.1.tgz#09c771de9e2d8169d5969adf298ae21581f08c7f"
|
||||
integrity sha512-z3H/HYI9MM0HTv3hQZ81f+AKb+yEoCRlUby1F80vbQ5XdzEMyY/9iNlAmhqiBKw4MJXwfgsh7ERGEOhrM1niMA==
|
||||
"@esbuild/linux-x64@0.27.0":
|
||||
version "0.27.0"
|
||||
resolved "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.0.tgz"
|
||||
integrity sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw==
|
||||
|
||||
"@esbuild/netbsd-arm64@0.27.1":
|
||||
version "0.27.1"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.1.tgz#475ac0ce7edf109a358b1669f67759de4bcbb7c4"
|
||||
integrity sha512-wzC24DxAvk8Em01YmVXyjl96Mr+ecTPyOuADAvjGg+fyBpGmxmcr2E5ttf7Im8D0sXZihpxzO1isus8MdjMCXQ==
|
||||
"@esbuild/netbsd-arm64@0.27.0":
|
||||
version "0.27.0"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.0.tgz#a0131159f4db6e490da35cc4bb51ef0d03b7848a"
|
||||
integrity sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w==
|
||||
|
||||
"@esbuild/netbsd-x64@0.27.1":
|
||||
version "0.27.1"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.27.1.tgz#3c31603d592477dc43b63df1ae100000f7fb59d7"
|
||||
integrity sha512-1YQ8ybGi2yIXswu6eNzJsrYIGFpnlzEWRl6iR5gMgmsrR0FcNoV1m9k9sc3PuP5rUBLshOZylc9nqSgymI+TYg==
|
||||
"@esbuild/netbsd-x64@0.27.0":
|
||||
version "0.27.0"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.27.0.tgz#6f4877d7c2ba425a2b80e4330594e0b43caa2d7d"
|
||||
integrity sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA==
|
||||
|
||||
"@esbuild/openbsd-arm64@0.27.1":
|
||||
version "0.27.1"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.1.tgz#482067c847665b10d66431e936d4bc5fa8025abf"
|
||||
integrity sha512-5Z+DzLCrq5wmU7RDaMDe2DVXMRm2tTDvX2KU14JJVBN2CT/qov7XVix85QoJqHltpvAOZUAc3ndU56HSMWrv8g==
|
||||
"@esbuild/openbsd-arm64@0.27.0":
|
||||
version "0.27.0"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.0.tgz#cbefbd4c2f375cebeb4f965945be6cf81331bd01"
|
||||
integrity sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ==
|
||||
|
||||
"@esbuild/openbsd-x64@0.27.1":
|
||||
version "0.27.1"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.27.1.tgz#687a188c2b184e5b671c5f74a6cd6247c0718c52"
|
||||
integrity sha512-Q73ENzIdPF5jap4wqLtsfh8YbYSZ8Q0wnxplOlZUOyZy7B4ZKW8DXGWgTCZmF8VWD7Tciwv5F4NsRf6vYlZtqg==
|
||||
"@esbuild/openbsd-x64@0.27.0":
|
||||
version "0.27.0"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.27.0.tgz#31fa9e8649fc750d7c2302c8b9d0e1547f57bc84"
|
||||
integrity sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A==
|
||||
|
||||
"@esbuild/openharmony-arm64@0.27.1":
|
||||
version "0.27.1"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.1.tgz#9929ee7fa8c1db2f33ef4d86198018dac9c1744f"
|
||||
integrity sha512-ajbHrGM/XiK+sXM0JzEbJAen+0E+JMQZ2l4RR4VFwvV9JEERx+oxtgkpoKv1SevhjavK2z2ReHk32pjzktWbGg==
|
||||
"@esbuild/openharmony-arm64@0.27.0":
|
||||
version "0.27.0"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.0.tgz#03727780f1fdf606e7b56193693a715d9f1ee001"
|
||||
integrity sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA==
|
||||
|
||||
"@esbuild/sunos-x64@0.27.1":
|
||||
version "0.27.1"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.27.1.tgz#94071a146f313e7394c6424af07b2b564f1f994d"
|
||||
integrity sha512-IPUW+y4VIjuDVn+OMzHc5FV4GubIwPnsz6ubkvN8cuhEqH81NovB53IUlrlBkPMEPxvNnf79MGBoz8rZ2iW8HA==
|
||||
"@esbuild/sunos-x64@0.27.0":
|
||||
version "0.27.0"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.27.0.tgz#866a35f387234a867ced35af8906dfffb073b9ff"
|
||||
integrity sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA==
|
||||
|
||||
"@esbuild/win32-arm64@0.27.1":
|
||||
version "0.27.1"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.27.1.tgz#869fde72a3576fdf48824085d05493fceebe395d"
|
||||
integrity sha512-RIVRWiljWA6CdVu8zkWcRmGP7iRRIIwvhDKem8UMBjPql2TXM5PkDVvvrzMtj1V+WFPB4K7zkIGM7VzRtFkjdg==
|
||||
"@esbuild/win32-arm64@0.27.0":
|
||||
version "0.27.0"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.27.0.tgz#53de43a9629b8a34678f28cd56cc104db1b67abb"
|
||||
integrity sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg==
|
||||
|
||||
"@esbuild/win32-ia32@0.27.1":
|
||||
version "0.27.1"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.27.1.tgz#31d7585893ed7b54483d0b8d87a4bfeba0ecfff5"
|
||||
integrity sha512-2BR5M8CPbptC1AK5JbJT1fWrHLvejwZidKx3UMSF0ecHMa+smhi16drIrCEggkgviBwLYd5nwrFLSl5Kho96RQ==
|
||||
"@esbuild/win32-ia32@0.27.0":
|
||||
version "0.27.0"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.27.0.tgz#924d2aed8692fea5d27bfb6500f9b8b9c1a34af4"
|
||||
integrity sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ==
|
||||
|
||||
"@esbuild/win32-x64@0.27.1":
|
||||
version "0.27.1"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.27.1.tgz#5efe5a112938b1180e98c76685ff9185cfa4f16e"
|
||||
integrity sha512-d5X6RMYv6taIymSk8JBP+nxv8DQAMY6A51GPgusqLdK9wBz5wWIXy1KjTck6HnjE9hqJzJRdk+1p/t5soSbCtw==
|
||||
"@esbuild/win32-x64@0.27.0":
|
||||
version "0.27.0"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.27.0.tgz#64995295227e001f2940258617c6674efb3ac48d"
|
||||
integrity sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg==
|
||||
|
||||
"@eslint-community/eslint-utils@^4.7.0":
|
||||
version "4.7.0"
|
||||
@@ -230,10 +230,10 @@
|
||||
minimatch "^3.1.2"
|
||||
strip-json-comments "^3.1.1"
|
||||
|
||||
"@eslint/js@9.39.2", "@eslint/js@^9.39.2":
|
||||
version "9.39.2"
|
||||
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.39.2.tgz#2d4b8ec4c3ea13c1b3748e0c97ecd766bdd80599"
|
||||
integrity sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==
|
||||
"@eslint/js@9.39.1", "@eslint/js@^9.39.1":
|
||||
version "9.39.1"
|
||||
resolved "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz"
|
||||
integrity sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==
|
||||
|
||||
"@eslint/object-schema@^2.1.7":
|
||||
version "2.1.7"
|
||||
@@ -1814,37 +1814,37 @@ esbuild-sass-plugin@^3.3.1:
|
||||
safe-identifier "^0.4.2"
|
||||
sass "^1.71.1"
|
||||
|
||||
esbuild@^0.27.1:
|
||||
version "0.27.1"
|
||||
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.27.1.tgz#56bf43e6a4b4d2004642ec7c091b78de02b0831a"
|
||||
integrity sha512-yY35KZckJJuVVPXpvjgxiCuVEJT67F6zDeVTv4rizyPrfGBUpZQsvmxnN+C371c2esD/hNMjj4tpBhuueLN7aA==
|
||||
esbuild@^0.27.0:
|
||||
version "0.27.0"
|
||||
resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.27.0.tgz"
|
||||
integrity sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==
|
||||
optionalDependencies:
|
||||
"@esbuild/aix-ppc64" "0.27.1"
|
||||
"@esbuild/android-arm" "0.27.1"
|
||||
"@esbuild/android-arm64" "0.27.1"
|
||||
"@esbuild/android-x64" "0.27.1"
|
||||
"@esbuild/darwin-arm64" "0.27.1"
|
||||
"@esbuild/darwin-x64" "0.27.1"
|
||||
"@esbuild/freebsd-arm64" "0.27.1"
|
||||
"@esbuild/freebsd-x64" "0.27.1"
|
||||
"@esbuild/linux-arm" "0.27.1"
|
||||
"@esbuild/linux-arm64" "0.27.1"
|
||||
"@esbuild/linux-ia32" "0.27.1"
|
||||
"@esbuild/linux-loong64" "0.27.1"
|
||||
"@esbuild/linux-mips64el" "0.27.1"
|
||||
"@esbuild/linux-ppc64" "0.27.1"
|
||||
"@esbuild/linux-riscv64" "0.27.1"
|
||||
"@esbuild/linux-s390x" "0.27.1"
|
||||
"@esbuild/linux-x64" "0.27.1"
|
||||
"@esbuild/netbsd-arm64" "0.27.1"
|
||||
"@esbuild/netbsd-x64" "0.27.1"
|
||||
"@esbuild/openbsd-arm64" "0.27.1"
|
||||
"@esbuild/openbsd-x64" "0.27.1"
|
||||
"@esbuild/openharmony-arm64" "0.27.1"
|
||||
"@esbuild/sunos-x64" "0.27.1"
|
||||
"@esbuild/win32-arm64" "0.27.1"
|
||||
"@esbuild/win32-ia32" "0.27.1"
|
||||
"@esbuild/win32-x64" "0.27.1"
|
||||
"@esbuild/aix-ppc64" "0.27.0"
|
||||
"@esbuild/android-arm" "0.27.0"
|
||||
"@esbuild/android-arm64" "0.27.0"
|
||||
"@esbuild/android-x64" "0.27.0"
|
||||
"@esbuild/darwin-arm64" "0.27.0"
|
||||
"@esbuild/darwin-x64" "0.27.0"
|
||||
"@esbuild/freebsd-arm64" "0.27.0"
|
||||
"@esbuild/freebsd-x64" "0.27.0"
|
||||
"@esbuild/linux-arm" "0.27.0"
|
||||
"@esbuild/linux-arm64" "0.27.0"
|
||||
"@esbuild/linux-ia32" "0.27.0"
|
||||
"@esbuild/linux-loong64" "0.27.0"
|
||||
"@esbuild/linux-mips64el" "0.27.0"
|
||||
"@esbuild/linux-ppc64" "0.27.0"
|
||||
"@esbuild/linux-riscv64" "0.27.0"
|
||||
"@esbuild/linux-s390x" "0.27.0"
|
||||
"@esbuild/linux-x64" "0.27.0"
|
||||
"@esbuild/netbsd-arm64" "0.27.0"
|
||||
"@esbuild/netbsd-x64" "0.27.0"
|
||||
"@esbuild/openbsd-arm64" "0.27.0"
|
||||
"@esbuild/openbsd-x64" "0.27.0"
|
||||
"@esbuild/openharmony-arm64" "0.27.0"
|
||||
"@esbuild/sunos-x64" "0.27.0"
|
||||
"@esbuild/win32-arm64" "0.27.0"
|
||||
"@esbuild/win32-ia32" "0.27.0"
|
||||
"@esbuild/win32-x64" "0.27.0"
|
||||
|
||||
escape-string-regexp@^4.0.0:
|
||||
version "4.0.0"
|
||||
@@ -1944,10 +1944,10 @@ eslint-visitor-keys@^4.2.1:
|
||||
resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz"
|
||||
integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==
|
||||
|
||||
eslint@^9.39.2:
|
||||
version "9.39.2"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.39.2.tgz#cb60e6d16ab234c0f8369a3fe7cc87967faf4b6c"
|
||||
integrity sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==
|
||||
eslint@^9.39.1:
|
||||
version "9.39.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.39.1.tgz#be8bf7c6de77dcc4252b5a8dcb31c2efff74a6e5"
|
||||
integrity sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils" "^4.8.0"
|
||||
"@eslint-community/regexpp" "^4.12.1"
|
||||
@@ -1955,7 +1955,7 @@ eslint@^9.39.2:
|
||||
"@eslint/config-helpers" "^0.4.2"
|
||||
"@eslint/core" "^0.17.0"
|
||||
"@eslint/eslintrc" "^3.3.1"
|
||||
"@eslint/js" "9.39.2"
|
||||
"@eslint/js" "9.39.1"
|
||||
"@eslint/plugin-kit" "^0.4.1"
|
||||
"@humanfs/node" "^0.16.6"
|
||||
"@humanwhocodes/module-importer" "^1.0.1"
|
||||
@@ -2304,10 +2304,10 @@ graphql@16.12.0:
|
||||
resolved "https://registry.npmjs.org/graphql/-/graphql-16.12.0.tgz"
|
||||
integrity sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==
|
||||
|
||||
gridstack@12.4.1:
|
||||
version "12.4.1"
|
||||
resolved "https://registry.yarnpkg.com/gridstack/-/gridstack-12.4.1.tgz#4a44511e5da33016e731f00bee279bed550d4ab9"
|
||||
integrity sha512-dYBNVEDw2zwnz0bCDouHk8rMclrMoMn4r6rtNyyWSeYsV3RF8QV2KFRTj4c86T2FsZPr3iQv+/LD/ae29FcpHQ==
|
||||
gridstack@12.3.3:
|
||||
version "12.3.3"
|
||||
resolved "https://registry.npmjs.org/gridstack/-/gridstack-12.3.3.tgz"
|
||||
integrity sha512-Bboi4gj7HXGnx1VFXQNde4Nwi5srdUSuCCnOSszKhFjBs8EtMEWhsKX02BjIKkErq/FjQUkNUbXUYeQaVMQ0jQ==
|
||||
|
||||
has-bigints@^1.0.1, has-bigints@^1.0.2:
|
||||
version "1.0.2"
|
||||
@@ -3061,10 +3061,10 @@ prettier-linter-helpers@^1.0.0:
|
||||
dependencies:
|
||||
fast-diff "^1.1.2"
|
||||
|
||||
prettier@^3.7.4:
|
||||
version "3.7.4"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.7.4.tgz#d2f8335d4b1cec47e1c8098645411b0c9dff9c0f"
|
||||
integrity sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==
|
||||
prettier@^3.7.3:
|
||||
version "3.7.3"
|
||||
resolved "https://registry.npmjs.org/prettier/-/prettier-3.7.3.tgz"
|
||||
integrity sha512-QgODejq9K3OzoBbuyobZlUhznP5SKwPqp+6Q6xw6o8gnhr4O85L2U915iM2IDcfF2NPXVaM9zlo9tdwipnYwzg==
|
||||
|
||||
punycode.js@^2.3.1:
|
||||
version "2.3.1"
|
||||
@@ -3251,10 +3251,10 @@ safe-regex-test@^1.1.0:
|
||||
es-errors "^1.3.0"
|
||||
is-regex "^1.2.1"
|
||||
|
||||
sass@1.97.0:
|
||||
version "1.97.0"
|
||||
resolved "https://registry.yarnpkg.com/sass/-/sass-1.97.0.tgz#8ed65df5e2f73012d5ef0e98837ff63550657ab2"
|
||||
integrity sha512-KR0igP1z4avUJetEuIeOdDlwaUDvkH8wSx7FdSjyYBS3dpyX3TzHfAMO0G1Q4/3cdjcmi3r7idh+KCmKqS+KeQ==
|
||||
sass@1.95.0:
|
||||
version "1.95.0"
|
||||
resolved "https://registry.yarnpkg.com/sass/-/sass-1.95.0.tgz#3a3a4d4d954313ab50eaf16f6e2548a2f6ec0811"
|
||||
integrity sha512-9QMjhLq+UkOg/4bb8Lt8A+hJZvY3t+9xeZMKSBtBEgxrXA3ed5Ts4NDreUkYgJP1BTmrscQE/xYhf7iShow6lw==
|
||||
dependencies:
|
||||
chokidar "^4.0.0"
|
||||
immutable "^5.0.2"
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
version: "4.5.0"
|
||||
version: "4.4.8"
|
||||
edition: "Community"
|
||||
published: "2025-12-16"
|
||||
designation: "beta1"
|
||||
published: "2025-12-09"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@ from django.db.models import Q
|
||||
|
||||
OBJECTPERMISSION_OBJECT_TYPES = Q(
|
||||
~Q(app_label__in=['account', 'admin', 'auth', 'contenttypes', 'sessions', 'taggit', 'users']) |
|
||||
Q(app_label='users', model__in=['objectpermission', 'token', 'group', 'user', 'owner'])
|
||||
Q(app_label='users', model__in=['objectpermission', 'token', 'group', 'user'])
|
||||
)
|
||||
|
||||
CONSTRAINT_TOKEN_USER = '$user'
|
||||
|
||||
@@ -67,16 +67,6 @@ class TestCase(_TestCase):
|
||||
obj_perm.users.add(self.user)
|
||||
obj_perm.object_types.add(object_type)
|
||||
|
||||
def remove_permissions(self, *names):
|
||||
"""
|
||||
Remove a set of permissions from the test user. Accepts permission names in the form <app>.<action>_<model>.
|
||||
"""
|
||||
for name in names:
|
||||
object_type, action = resolve_permission_type(name)
|
||||
ObjectPermission.objects.filter(
|
||||
actions__contains=[action], object_types=object_type, users=self.user
|
||||
).delete()
|
||||
|
||||
#
|
||||
# Custom assertions
|
||||
#
|
||||
|
||||
@@ -15,7 +15,6 @@ from ipam.models import VLAN
|
||||
from netbox.api.fields import ChoiceField, SerializedPKRelatedField
|
||||
from netbox.api.serializers import NetBoxModelSerializer, PrimaryModelSerializer
|
||||
from tenancy.api.serializers_.tenants import TenantSerializer
|
||||
from users.api.serializers_.mixins import OwnerMixin
|
||||
from virtualization.choices import *
|
||||
from virtualization.models import VirtualDisk, VirtualMachine, VMInterface
|
||||
from vpn.api.serializers_.l2vpn import L2VPNTerminationSerializer
|
||||
@@ -66,8 +65,8 @@ class VirtualMachineWithConfigContextSerializer(VirtualMachineSerializer):
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'name', 'status', 'start_on_boot', 'site', 'cluster', 'device',
|
||||
'serial', 'role', 'tenant', 'platform', 'primary_ip', 'primary_ip4', 'primary_ip6', 'vcpus', 'memory',
|
||||
'disk', 'description', 'owner', 'comments', 'config_template', 'local_context_data', 'tags',
|
||||
'custom_fields', 'config_context', 'created', 'last_updated', 'interface_count', 'virtual_disk_count',
|
||||
'disk', 'description', 'comments', 'config_template', 'local_context_data', 'tags', 'custom_fields',
|
||||
'config_context', 'created', 'last_updated', 'interface_count', 'virtual_disk_count',
|
||||
]
|
||||
|
||||
@extend_schema_field(serializers.JSONField(allow_null=True))
|
||||
@@ -79,7 +78,7 @@ class VirtualMachineWithConfigContextSerializer(VirtualMachineSerializer):
|
||||
# VM interfaces
|
||||
#
|
||||
|
||||
class VMInterfaceSerializer(OwnerMixin, NetBoxModelSerializer):
|
||||
class VMInterfaceSerializer(NetBoxModelSerializer):
|
||||
virtual_machine = VirtualMachineSerializer(nested=True)
|
||||
parent = NestedVMInterfaceSerializer(required=False, allow_null=True)
|
||||
bridge = NestedVMInterfaceSerializer(required=False, allow_null=True)
|
||||
@@ -108,7 +107,7 @@ class VMInterfaceSerializer(OwnerMixin, NetBoxModelSerializer):
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'virtual_machine', 'name', 'enabled', 'parent', 'bridge', 'mtu',
|
||||
'mac_address', 'primary_mac_address', 'mac_addresses', 'description', 'mode', 'untagged_vlan',
|
||||
'tagged_vlans', 'qinq_svlan', 'vlan_translation_policy', 'vrf', 'l2vpn_termination', 'owner', 'tags',
|
||||
'tagged_vlans', 'qinq_svlan', 'vlan_translation_policy', 'vrf', 'l2vpn_termination', 'tags',
|
||||
'custom_fields', 'created', 'last_updated', 'count_ipaddresses', 'count_fhrp_groups',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'virtual_machine', 'name', 'description')
|
||||
@@ -148,13 +147,13 @@ class VMInterfaceSerializer(OwnerMixin, NetBoxModelSerializer):
|
||||
# Virtual Disk
|
||||
#
|
||||
|
||||
class VirtualDiskSerializer(OwnerMixin, NetBoxModelSerializer):
|
||||
class VirtualDiskSerializer(NetBoxModelSerializer):
|
||||
virtual_machine = VirtualMachineSerializer(nested=True)
|
||||
|
||||
class Meta:
|
||||
model = VirtualDisk
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'virtual_machine', 'name', 'description', 'size', 'owner', 'tags',
|
||||
'id', 'url', 'display_url', 'display', 'virtual_machine', 'name', 'description', 'size', 'tags',
|
||||
'custom_fields', 'created', 'last_updated',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'virtual_machine', 'name', 'description', 'size')
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('contenttypes', '0002_remove_content_type_name'),
|
||||
('dcim', '0225_gfk_indexes'),
|
||||
('extras', '0134_owner'),
|
||||
('tenancy', '0022_add_comments_to_organizationalmodel'),
|
||||
('users', '0015_owner'),
|
||||
('virtualization', '0051_add_comments_to_organizationalmodel'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddIndex(
|
||||
model_name='cluster',
|
||||
index=models.Index(fields=['scope_type', 'scope_id'], name='virtualizat_scope_t_fb3b6e_idx'),
|
||||
),
|
||||
]
|
||||
@@ -107,9 +107,6 @@ class Cluster(ContactsMixin, CachedScopeMixin, PrimaryModel):
|
||||
name='%(app_label)s_%(class)s_unique__site_name'
|
||||
),
|
||||
)
|
||||
indexes = (
|
||||
models.Index(fields=('scope_type', 'scope_id')),
|
||||
)
|
||||
verbose_name = _('cluster')
|
||||
verbose_name_plural = _('clusters')
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ from django.urls import reverse
|
||||
|
||||
from dcim.choices import InterfaceModeChoices
|
||||
from dcim.models import DeviceRole, Platform, Site
|
||||
from extras.models import ConfigTemplate
|
||||
from ipam.models import VLAN, VRF
|
||||
from utilities.testing import ViewTestCases, create_tags, create_test_device, create_test_virtualmachine
|
||||
from virtualization.choices import *
|
||||
@@ -327,28 +326,6 @@ class VirtualMachineTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
url = reverse('virtualization:virtualmachine_interfaces', kwargs={'pk': virtualmachine.pk})
|
||||
self.assertHttpStatus(self.client.get(url), 200)
|
||||
|
||||
def test_virtualmachine_renderconfig(self):
|
||||
configtemplate = ConfigTemplate.objects.create(
|
||||
name='Test Config Template',
|
||||
template_code='Config for VM {{ virtualmachine.name }}'
|
||||
)
|
||||
vm = VirtualMachine.objects.first()
|
||||
vm.config_template = configtemplate
|
||||
vm.save()
|
||||
url = reverse('virtualization:virtualmachine_render-config', kwargs={'pk': vm.pk})
|
||||
|
||||
# User with only view permission should NOT be able to render config
|
||||
self.add_permissions('virtualization.view_virtualmachine')
|
||||
self.assertHttpStatus(self.client.get(url), 403)
|
||||
|
||||
# With render_config permission added should be able to render config
|
||||
self.add_permissions('virtualization.render_config_virtualmachine')
|
||||
self.assertHttpStatus(self.client.get(url), 200)
|
||||
|
||||
# With view permission removed should NOT be able to render config
|
||||
self.remove_permissions('virtualization.view_virtualmachine')
|
||||
self.assertHttpStatus(self.client.get(url), 403)
|
||||
|
||||
|
||||
class VMInterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
||||
model = VMInterface
|
||||
|
||||
@@ -405,7 +405,6 @@ class VirtualMachineConfigContextView(ObjectConfigContextView):
|
||||
class VirtualMachineRenderConfigView(ObjectRenderConfigView):
|
||||
queryset = VirtualMachine.objects.all()
|
||||
base_template = 'virtualization/virtualmachine/base.html'
|
||||
additional_permissions = ['virtualization.render_config_virtualmachine']
|
||||
tab = ViewTab(
|
||||
label=_('Render Config'),
|
||||
weight=2100,
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('contenttypes', '0002_remove_content_type_name'),
|
||||
('dcim', '0225_gfk_indexes'),
|
||||
('extras', '0134_owner'),
|
||||
('ipam', '0086_gfk_indexes'),
|
||||
('tenancy', '0022_add_comments_to_organizationalmodel'),
|
||||
('users', '0015_owner'),
|
||||
('wireless', '0016_owner'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddIndex(
|
||||
model_name='wirelesslan',
|
||||
index=models.Index(fields=['scope_type', 'scope_id'], name='wireless_wi_scope_t_6740a3_idx'),
|
||||
),
|
||||
]
|
||||
@@ -113,9 +113,6 @@ class WirelessLAN(WirelessAuthenticationBase, CachedScopeMixin, PrimaryModel):
|
||||
|
||||
class Meta:
|
||||
ordering = ('ssid', 'pk')
|
||||
indexes = (
|
||||
models.Index(fields=('scope_type', 'scope_id')),
|
||||
)
|
||||
verbose_name = _('wireless LAN')
|
||||
verbose_name_plural = _('wireless LANs')
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
[project]
|
||||
name = "netbox"
|
||||
version = "4.5.0-beta1"
|
||||
requires-python = ">=3.12"
|
||||
version = "4.4.7"
|
||||
requires-python = ">=3.10"
|
||||
description = "The premier source of truth powering network automation."
|
||||
readme = "README.md"
|
||||
license = "Apache-2.0"
|
||||
|
||||
@@ -36,8 +36,8 @@ rq==2.6.1
|
||||
social-auth-app-django==5.6.0
|
||||
social-auth-core==4.8.1
|
||||
sorl-thumbnail==12.11.0
|
||||
strawberry-graphql==0.287.3
|
||||
strawberry-graphql==0.287.2
|
||||
strawberry-graphql-django==0.70.1
|
||||
svgwrite==1.4.3
|
||||
tablib==3.9.0
|
||||
tzdata==2025.3
|
||||
tzdata==2025.2
|
||||
|
||||
Reference in New Issue
Block a user