Compare commits

...

19 Commits

Author SHA1 Message Date
Jason Novinger
6ca3908715 Release v4.3.5 (#19975)
* Release v4.3.5

* Fix missing strawberry-graphql==0.278.0 specifier
2025-07-29 15:28:49 -05:00
Jason Novinger
c736ce3179 Fixes #18900: raise QuerySetNotOrdered exception when trying to paginate unordered API querysets (#19943)
* Fixes #18900: introduce/raise QuerySetNotOrdered exception

Defines a new exception, `QuerySetNotOrdered`, and raises it in
`OptionalLimitOffsetPagination.paginate_queryset` in the right
conditions:
- the iterable to be paginated is a QuerySet isinstance
- the `queryset.ordered` flag is not truthy

* Don't try to reapply ordering if ordering is already present

* Add ordering for failing tagged-objects list API endpoint

I chose to implement this here for TaggedItemViewSet, rather than on the
model, because any meaningful ordering is going to be done on the
related Tag instance and I didn't want to introduce potential, not well
understood side-effects by applying a model-wide ordering via a related
model field.

* Add default Token ordering behavior

* Adds basic tests for raising QuerySetNotOrdered

* Note why ordering is not applied in TaggedItem.Meta
2025-07-29 11:49:36 -05:00
Martin Rødvand
111fefdf9c Fix #19910: Add conditional to hide internet dependent links in an isolated deployment (#19951)
* Add conditional to hide internet dependent links in an isolated deployment

* Formatting

* Adjust conditional

* Formatting
2025-07-29 10:41:32 -05:00
Jeremy Stretch
063d1fef7a Closes #18797: Support path import for certain Jinja environment parameters (#19962)
* Closes #18797: Support path import for certain Jinja environment parameters

* Document dotted path support for Jinja env params
2025-07-29 09:09:25 -05:00
Jathn
6ba6ff3fee Fixes #19764: docs/administration replicating netbox - wrong table name 2025-07-29 08:57:49 -04:00
github-actions
7bb7307892 Update source translation strings 2025-07-29 05:10:14 +00:00
Jeremy Stretch
c2d3363930 Closes #18399: Refactor logic for marking data source syncing as queued (#19960) 2025-07-28 09:04:38 -07:00
Jeremy Stretch
6e30c11017 Fixes #19956: Prevent duplicate deletion records from cascading deletions 2025-07-28 09:49:08 -04:00
github-actions
b01c75cf3a Update source translation strings 2025-07-25 05:07:26 +00:00
Jonathan Ramstedt
ffa9a52667 Closes #18936: add color name support for cable bulk import (#19949) 2025-07-24 09:54:49 -07:00
bctiemann
47320f9958 Merge pull request #19912 from miaow2/19903-regexp
Closes #19903: Add `regex` and `iregex` filter lookup expressions and corresponding tests
2025-07-24 12:32:19 -04:00
github-actions
d08a1bd07d Update source translation strings 2025-07-24 05:05:44 +00:00
Martin Hauser
14c4aeca54 Closes #19840 - Enable Site Filtering for Devices in Cable Bulk Import (#19923)
* feat(dcim): Add site fields to Cable bulk import form

Introduces `side_a_site` and `side_b_site` fields for the Cable bulk
import form. Limits device choices on both sides to the selected site
for improved input validation and consistency.

* feat(dcim): Enhance test data setup with multiple sites

Refactors tests to create multiple sites and assign devices accordingly.
Updates CSV data to include `side_a_site` and `side_b_site` fields for
scenarios involving multiple sites. This improves test coverage and
alignment with real-world use cases.

* docs(dcim): Update comments explaining indent for CSV import

Improved the inline comments to clarify the rationale behind allowing
devices with duplicate names on different sites during CSV bulk import.
2025-07-23 15:50:05 -05:00
Jason Novinger
26bec1275f Fixes #19934: add description field to Tenant bulk edit form (#19937) 2025-07-23 13:41:00 -07:00
Jason Novinger
fa2d7f6516 Fixes #19916: restore Rack device representation behavior
The select list of 'Images and Label', 'Images Only', and 'Label Only'
was broken during recent work while implementing #19823.

This fixes the issue by placing the `rack_elevation` class attribute on
the <div> element that contains the SVG after being loaded by HTMX. In
addition, we needed to slightly modify the selectors in the frontend
code that looked for the elements within the SVG to hide and/or show.
Previously, it was looking inside of a contentDocument embedded in an
<object> element. The simplified version just looks inside of the
SVG containing div.
2025-07-23 08:45:40 -04:00
Marco Spizzuoco
d571cb4867 Closes #19902: add clip path to avoid overflow of device name, truncate text to improve centering (#19913) 2025-07-22 09:44:14 -07:00
bluikko
2129355c30 Closes #19926: Remove RHEL firewalld note
Closes: #19926
2025-07-22 08:04:53 -04:00
Artem Kotik
c40bfb1445 Add regex and iregex filter lookup expressions and corresponding tests 2025-07-18 16:56:54 +02:00
github-actions
b88b5b0b1b Update source translation strings 2025-07-16 05:06:12 +00:00
69 changed files with 5941 additions and 5176 deletions

View File

@@ -15,7 +15,7 @@ body:
attributes:
label: NetBox version
description: What version of NetBox are you currently running?
placeholder: v4.3.4
placeholder: v4.3.5
validations:
required: true
- type: dropdown

View File

@@ -27,7 +27,7 @@ body:
attributes:
label: NetBox Version
description: What version of NetBox are you currently running?
placeholder: v4.3.4
placeholder: v4.3.5
validations:
required: true
- type: dropdown

View File

@@ -8,7 +8,9 @@ django-cors-headers
# Runtime UI tool for debugging Django
# https://github.com/jazzband/django-debug-toolbar/blob/main/docs/changes.rst
django-debug-toolbar
# django-debug-toolbar v6.0.0 raises "Attribute Error at /: 'function' object has no attribute 'set'"
# see https://github.com/netbox-community/netbox/issues/19974
django-debug-toolbar==5.2.0
# Library for writing reusable URL query filters
# https://github.com/carltongibson/django-filter/blob/main/CHANGES.rst
@@ -145,8 +147,7 @@ strawberry-graphql
# Strawberry GraphQL Django extension
# https://github.com/strawberry-graphql/strawberry-django/releases
# See #19771
strawberry-graphql-django==0.60.0
strawberry-graphql-django
# SVG image rendering (used for rack elevations)
# https://github.com/mozman/svgwrite/blob/master/NEWS.rst

View File

@@ -18,10 +18,10 @@ pg_dump --username netbox --password --host localhost netbox > netbox.sql
!!! note
You may need to change the username, host, and/or database in the command above to match your installation.
When replicating a production database for development purposes, you may find it convenient to exclude changelog data, which can easily account for the bulk of a database's size. To do this, exclude the `extras_objectchange` table data from the export. The table will still be included in the output file, but will not be populated with any data.
When replicating a production database for development purposes, you may find it convenient to exclude changelog data, which can easily account for the bulk of a database's size. To do this, exclude the `core_objectchange` table data from the export. The table will still be included in the output file, but will not be populated with any data.
```no-highlight
pg_dump ... --exclude-table-data=extras_objectchange netbox > netbox.sql
pg_dump ... --exclude-table-data=core_objectchange netbox > netbox.sql
```
### Load an Exported Database

View File

@@ -302,13 +302,6 @@ Quit the server with CONTROL-C.
Next, connect to the name or IP of the server (as defined in `ALLOWED_HOSTS`) on port 8000; for example, <http://127.0.0.1:8000/>. You should be greeted with the NetBox home page. Try logging in using the username and password specified when creating a superuser.
!!! note
By default RHEL based distros will likely block your testing attempts with firewalld. The development server port can be opened with `firewall-cmd` (add `--permanent` if you want the rule to survive server restarts):
```no-highlight
firewall-cmd --zone=public --add-port=8000/tcp
```
!!! danger "Not for production use"
The development server is for development and testing purposes only. It is neither performant nor secure enough for production use. **Do not use it in production.**

View File

@@ -24,6 +24,14 @@ Jinja2 template code, if being defined locally rather than replicated from a dat
A dictionary of any additional parameters to pass when instantiating the [Jinja2 environment](https://jinja.palletsprojects.com/en/3.1.x/api/#jinja2.Environment). Jinja2 supports various optional parameters which can be used to modify its default behavior.
The `undefined` and `finalize` Jinja environment parameters, which must reference a Python class or function, can define a dotted path to the desired resource. For example:
```json
{
"undefined": "jinja2.StrictUndefined"
}
```
### MIME Type
!!! info "This field was introduced in NetBox v4.3."

View File

@@ -26,6 +26,14 @@ Jinja2 template code for rendering the exported data.
A dictionary of any additional parameters to pass when instantiating the [Jinja2 environment](https://jinja.palletsprojects.com/en/3.1.x/api/#jinja2.Environment). Jinja2 supports various optional parameters which can be used to modify its default behavior.
The `undefined` and `finalize` Jinja environment parameters, which must reference a Python class or function, can define a dotted path to the desired resource. For example:
```json
{
"undefined": "jinja2.StrictUndefined"
}
```
### MIME Type
The MIME type to indicate in the response when rendering the export template (optional). Defaults to `text/plain`.

View File

@@ -80,18 +80,20 @@ GET /api/ipam/vlans/?vid__gt=900
String based (char) fields (Name, Address, etc) support these lookup expressions:
| Filter | Description |
|---------|----------------------------------------|
| `n` | Not equal to |
| `ic` | Contains (case-insensitive) |
| `nic` | Does not contain (case-insensitive) |
| `isw` | Starts with (case-insensitive) |
| `nisw` | Does not start with (case-insensitive) |
| `iew` | Ends with (case-insensitive) |
| `niew` | Does not end with (case-insensitive) |
| `ie` | Exact match (case-insensitive) |
| `nie` | Inverse exact match (case-insensitive) |
| `empty` | Is empty/null (boolean) |
| Filter | Description |
|----------|----------------------------------------|
| `n` | Not equal to |
| `ic` | Contains (case-insensitive) |
| `nic` | Does not contain (case-insensitive) |
| `isw` | Starts with (case-insensitive) |
| `nisw` | Does not start with (case-insensitive) |
| `iew` | Ends with (case-insensitive) |
| `niew` | Does not end with (case-insensitive) |
| `ie` | Exact match (case-insensitive) |
| `nie` | Inverse exact match (case-insensitive) |
| `empty` | Is empty/null (boolean) |
| `regex` | Regexp matching |
| `iregex` | Regexp matching (case-insensitive) |
Here is an example of a lookup expression on a string field that will return all devices with `switch` in the name:

View File

@@ -1,5 +1,22 @@
# NetBox v4.3
## v4.3.5 (2025-07-29)
### Enhancements
* [#18399](https://github.com/netbox-community/netbox/issues/18399) - Data source synchronization jobs now properly show "queued" status when enqueued
* [#18797](https://github.com/netbox-community/netbox/issues/18797) - Added jinja2.StrictUndefined option for config template rendering to catch undefined variables
* [#18936](https://github.com/netbox-community/netbox/issues/18936) - Cable imports now accept color names (e.g. "red", "blue") in addition to hex color codes
* [#19840](https://github.com/netbox-community/netbox/issues/19840) - Cable imports now support specifying site information for better organization
* [#19902](https://github.com/netbox-community/netbox/issues/19902) - Device names in rack elevation SVG exports are automatically truncated to prevent overflow beyond rack unit boundaries
* [#19903](https://github.com/netbox-community/netbox/issues/19903) - String field filters now support `regex` and `iregex` lookups for advanced pattern matching
* [#19910](https://github.com/netbox-community/netbox/issues/19910) - Internet-dependent links are no longer visible when running in air-gapped environments
### Bug Fixes
* [#18900](https://github.com/netbox-community/netbox/issues/18900) - REST API paginator now raises proper exceptions when attempting to paginate unordered querysets
* [#19916](https://github.com/netbox-community/netbox/issues/19916) - Rack elevation image/label dropdown functionality restored
* [#19934](https://github.com/netbox-community/netbox/issues/19934) - Added missing description field to tenant bulk edit form
* [#19956](https://github.com/netbox-community/netbox/issues/19956) - Prevent duplicate deletion records in changelog from cascading deletions
## v4.3.4 (2025-07-15)
### Enhancements

View File

@@ -1,29 +1,28 @@
from django.http import Http404, HttpResponse
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext_lazy as _
from django_rq.queues import get_redis_connection
from django_rq.settings import QUEUES_LIST
from django_rq.utils import get_statistics
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import extend_schema
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.exceptions import PermissionDenied
from rest_framework.permissions import IsAdminUser
from rest_framework.response import Response
from rest_framework.routers import APIRootView
from rest_framework.viewsets import ReadOnlyModelViewSet
from rq.job import Job as RQ_Job
from rq.worker import Worker
from core import filtersets
from core.choices import DataSourceStatusChoices
from core.jobs import SyncDataSourceJob
from core.models import *
from core.utils import delete_rq_job, enqueue_rq_job, get_rq_jobs, requeue_rq_job, stop_rq_job
from django_rq.queues import get_redis_connection
from django_rq.utils import get_statistics
from django_rq.settings import QUEUES_LIST
from netbox.api.metadata import ContentTypeMetadata
from netbox.api.pagination import LimitOffsetListPagination
from netbox.api.viewsets import NetBoxModelViewSet, NetBoxReadOnlyModelViewSet
from rest_framework import viewsets
from rest_framework.permissions import IsAdminUser
from rq.job import Job as RQ_Job
from rq.worker import Worker
from . import serializers
@@ -50,10 +49,8 @@ class DataSourceViewSet(NetBoxModelViewSet):
if not request.user.has_perm('core.sync_datasource', obj=datasource):
raise PermissionDenied(_("This user does not have permission to synchronize this data source."))
# Enqueue the sync job & update the DataSource's status
# Enqueue the sync job
SyncDataSourceJob.enqueue(instance=datasource, user=request.user)
datasource.status = DataSourceStatusChoices.QUEUED
DataSource.objects.filter(pk=datasource.pk).update(status=datasource.status)
serializer = serializers.DataSourceSerializer(datasource, context={'request': request})

View File

@@ -21,6 +21,17 @@ class SyncDataSourceJob(JobRunner):
class Meta:
name = 'Synchronization'
@classmethod
def enqueue(cls, *args, **kwargs):
job = super().enqueue(*args, **kwargs)
# Update the DataSource's synchronization status to queued
if datasource := job.object:
datasource.status = DataSourceStatusChoices.QUEUED
DataSource.objects.filter(pk=datasource.pk).update(status=datasource.status)
return job
def run(self, *args, **kwargs):
datasource = DataSource.objects.get(pk=self.job.object_id)

View File

@@ -1,10 +1,12 @@
import logging
from threading import local
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.db.models.fields.reverse_related import ManyToManyRel, ManyToOneRel
from django.db.models.signals import m2m_changed, post_save, pre_delete
from django.dispatch import receiver, Signal
from django.core.signals import request_finished
from django.utils.translation import gettext_lazy as _
from django_prometheus.models import model_deletes, model_inserts, model_updates
@@ -42,6 +44,10 @@ clear_events = Signal()
# Change logging & event handling
#
# Used to track received signals per object
_signals_received = local()
@receiver((post_save, m2m_changed))
def handle_changed_object(sender, instance, **kwargs):
"""
@@ -130,6 +136,16 @@ def handle_deleted_object(sender, instance, **kwargs):
if request is None:
return
# Check whether we've already processed a pre_delete signal for this object. (This can
# happen e.g. when both a parent object and its child are deleted simultaneously, due
# to cascading deletion.)
if not hasattr(_signals_received, 'pre_delete'):
_signals_received.pre_delete = set()
signature = (ContentType.objects.get_for_model(instance), instance.pk)
if signature in _signals_received.pre_delete:
return
_signals_received.pre_delete.add(signature)
# Record an ObjectChange if applicable
if hasattr(instance, 'to_objectchange'):
if hasattr(instance, 'snapshot') and not getattr(instance, '_prechange_snapshot', None):
@@ -179,6 +195,14 @@ def handle_deleted_object(sender, instance, **kwargs):
model_deletes.labels(instance._meta.model_name).inc()
@receiver(request_finished)
def clear_signal_history(sender, **kwargs):
"""
Clear out the signals history once the request is finished.
"""
_signals_received.pre_delete = set()
@receiver(clear_events)
def clear_events_queue(sender, **kwargs):
"""

View File

@@ -346,6 +346,38 @@ class ChangeLogViewTest(ModelViewTestCase):
self.assertEqual(changes[1].changed_object_type, ContentType.objects.get_for_model(Interface))
self.assertEqual(changes[2].changed_object_type, ContentType.objects.get_for_model(Device))
def test_duplicate_deletions(self):
"""
Check that a cascading deletion event does not generate multiple "deleted" ObjectChange records for
the same object.
"""
role1 = DeviceRole(name='Role 1', slug='role-1')
role1.save()
role2 = DeviceRole(name='Role 2', slug='role-2', parent=role1)
role2.save()
pk_list = [role1.pk, role2.pk]
# Delete both objects simultaneously
form_data = {
'pk': pk_list,
'confirm': True,
'_confirm': True,
}
request = {
'path': reverse('dcim:devicerole_bulk_delete'),
'data': post_data(form_data),
}
self.add_permissions('dcim.delete_devicerole')
self.assertHttpStatus(self.client.post(**request), 302)
# This should result in exactly one change record per object
objectchanges = ObjectChange.objects.filter(
changed_object_type=ContentType.objects.get_for_model(DeviceRole),
changed_object_id__in=pk_list,
action=ObjectChangeActionChoices.ACTION_DELETE
)
self.assertEqual(objectchanges.count(), 2)
class ChangeLogAPITest(APITestCase):

View File

@@ -33,7 +33,6 @@ from utilities.json import ConfigJSONEncoder
from utilities.query import count_related
from utilities.views import ContentTypePermissionRequiredMixin, GetRelatedModelsMixin, register_model_view
from . import filtersets, forms, tables
from .choices import DataSourceStatusChoices
from .jobs import SyncDataSourceJob
from .models import *
from .plugins import get_catalog_plugins, get_local_plugins
@@ -78,12 +77,8 @@ class DataSourceSyncView(BaseObjectView):
def post(self, request, pk):
datasource = get_object_or_404(self.queryset, pk=pk)
# Enqueue the sync job & update the DataSource's status
# Enqueue the sync job
job = SyncDataSourceJob.enqueue(instance=datasource, user=request.user)
datasource.status = DataSourceStatusChoices.QUEUED
DataSource.objects.filter(pk=datasource.pk).update(status=datasource.status)
messages.success(
request,
_("Queued job #{id} to sync {datasource}").format(id=job.pk, datasource=datasource)

View File

@@ -1335,6 +1335,13 @@ class MACAddressImportForm(NetBoxModelImportForm):
class CableImportForm(NetBoxModelImportForm):
# Termination A
side_a_site = CSVModelChoiceField(
label=_('Side A site'),
queryset=Site.objects.all(),
required=False,
to_field_name='name',
help_text=_('Site of parent device A (if any)'),
)
side_a_device = CSVModelChoiceField(
label=_('Side A device'),
queryset=Device.objects.all(),
@@ -1353,6 +1360,13 @@ class CableImportForm(NetBoxModelImportForm):
)
# Termination B
side_b_site = CSVModelChoiceField(
label=_('Side B site'),
queryset=Site.objects.all(),
required=False,
to_field_name='name',
help_text=_('Site of parent device B (if any)'),
)
side_b_device = CSVModelChoiceField(
label=_('Side B device'),
queryset=Device.objects.all(),
@@ -1396,14 +1410,39 @@ class CableImportForm(NetBoxModelImportForm):
required=False,
help_text=_('Length unit')
)
color = forms.CharField(
label=_('Color'),
required=False,
max_length=16,
help_text=_('Color name (e.g. "Red") or hex code (e.g. "f44336")')
)
class Meta:
model = Cable
fields = [
'side_a_device', 'side_a_type', 'side_a_name', 'side_b_device', 'side_b_type', 'side_b_name', 'type',
'status', 'tenant', 'label', 'color', 'length', 'length_unit', 'description', 'comments', 'tags',
'side_a_site', 'side_a_device', 'side_a_type', 'side_a_name', 'side_b_site', 'side_b_device', 'side_b_type',
'side_b_name', 'type', 'status', 'tenant', 'label', 'color', 'length', 'length_unit', 'description',
'comments', 'tags',
]
def __init__(self, data=None, *args, **kwargs):
super().__init__(data, *args, **kwargs)
if data:
# Limit choices for side_a_device to the assigned side_a_site
if side_a_site := data.get('side_a_site'):
side_a_device_params = {f'site__{self.fields["side_a_site"].to_field_name}': side_a_site}
self.fields['side_a_device'].queryset = self.fields['side_a_device'].queryset.filter(
**side_a_device_params
)
# Limit choices for side_b_device to the assigned side_b_site
if side_b_site := data.get('side_b_site'):
side_b_device_params = {f'site__{self.fields["side_b_site"].to_field_name}': side_b_site}
self.fields['side_b_device'].queryset = self.fields['side_b_device'].queryset.filter(
**side_b_device_params
)
def _clean_side(self, side):
"""
Derive a Cable's A/B termination objects.
@@ -1440,6 +1479,24 @@ class CableImportForm(NetBoxModelImportForm):
setattr(self.instance, f'{side}_terminations', [termination_object])
return termination_object
def _clean_color(self, color):
"""
Derive a colors hex code
:param color: color as hex or color name
"""
color_parsed = color.strip().lower()
for hex_code, label in ColorChoices.CHOICES:
if color.lower() == label.lower():
color_parsed = hex_code
if len(color_parsed) > 6:
raise forms.ValidationError(
_(f"{color} did not match any used color name and was longer than six characters: invalid hex.")
)
return color_parsed
def clean_side_a_name(self):
return self._clean_side('a')
@@ -1451,11 +1508,14 @@ class CableImportForm(NetBoxModelImportForm):
length_unit = self.cleaned_data.get('length_unit', None)
return length_unit if length_unit is not None else ''
def clean_color(self):
color = self.cleaned_data.get('color', None)
return self._clean_color(color) if color is not None else ''
#
# Virtual chassis
#
class VirtualChassisImportForm(NetBoxModelImportForm):
master = CSVModelChoiceField(
label=_('Master'),

View File

@@ -3,6 +3,7 @@ import svgwrite
from svgwrite.container import Hyperlink
from svgwrite.image import Image
from svgwrite.gradients import LinearGradient
from svgwrite.masking import ClipPath
from svgwrite.shapes import Rect
from svgwrite.text import Text
@@ -67,6 +68,20 @@ def get_device_description(device):
return description
def truncate_text(text, width, font_size=15):
"""
Truncate text to fit within the width of a rectangle.
:param text: The text to truncate
:param width: Width of rectangle
:param font_size: Font size (default is 15, ~0.875rem)
"""
char_width = font_size * 0.6 # 0.6 is an approximation of the average character width in pixels
max_char = int(width / char_width)
return text if len(text) <= max_char else text[:max_char] + '...'
class RackElevationSVG:
"""
Use this class to render a rack elevation as an SVG image.
@@ -177,12 +192,26 @@ class RackElevationSVG:
link = Hyperlink(href=f'{self.base_url}{device.get_absolute_url()}', target="_parent")
link.set_desc(description)
# Create clipPath element
# This is necessary as fallback because the truncate_text method is an approximation
clip_id = f"clip-{device.id}"
clip_path = ClipPath(id=clip_id)
clip_path.add(Rect(coords, size))
self.drawing.defs.add(clip_path)
# Name to display
display_name = truncate_text(name, size[0])
# Add rect element to hyperlink
if color:
link.add(Rect(coords, size, style=f'fill: #{color}', class_=f'slot{css_extra}'))
else:
link.add(Rect(coords, size, class_=f'slot blocked{css_extra}'))
link.add(Text(name, insert=text_coords, fill=text_color, class_=f'label{css_extra}'))
link.add(
Text(display_name, insert=text_coords, fill=text_color, clip_path=f"url(#{clip_id})",
class_=f'label{css_extra}')
)
# Embed device type image if provided
if self.include_images and image:

View File

@@ -3266,17 +3266,27 @@ class CableTestCase(
@classmethod
def setUpTestData(cls):
site = Site.objects.create(name='Site 1', slug='site-1')
sites = (
Site(name='Site 1', slug='site-1'),
Site(name='Site 2', slug='site-2'),
)
Site.objects.bulk_create(sites)
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
devicetype = DeviceType.objects.create(model='Device Type 1', manufacturer=manufacturer)
role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1')
vc = VirtualChassis.objects.create(name='Virtual Chassis')
# NOTE: By design, NetBox now allows for the creation of devices with the same name if they belong to
# different sites.
# The CSV test below demonstrates that devices with identical names on different sites can be created
# and referenced successfully.
devices = (
Device(name='Device 1', site=site, device_type=devicetype, role=role),
Device(name='Device 2', site=site, device_type=devicetype, role=role),
Device(name='Device 3', site=site, device_type=devicetype, role=role),
Device(name='Device 4', site=site, device_type=devicetype, role=role),
# Create 'Device 1' assigned to 'Site 1'
Device(name='Device 1', site=sites[0], device_type=devicetype, role=role),
Device(name='Device 2', site=sites[0], device_type=devicetype, role=role),
Device(name='Device 3', site=sites[0], device_type=devicetype, role=role),
# Create 'Device 1' assigned to 'Site 2' (allowed since the site is different)
Device(name='Device 1', site=sites[1], device_type=devicetype, role=role),
)
Device.objects.bulk_create(devices)
@@ -3327,13 +3337,15 @@ class CableTestCase(
'tags': [t.pk for t in tags],
}
# Ensure that CSV bulk import supports assigning terminations from parent devices that share
# the same device name, provided those devices belong to different sites.
cls.csv_data = (
"side_a_device,side_a_type,side_a_name,side_b_device,side_b_type,side_b_name",
"Device 3,dcim.interface,Interface 1,Device 4,dcim.interface,Interface 1",
"Device 3,dcim.interface,Interface 2,Device 4,dcim.interface,Interface 2",
"Device 3,dcim.interface,Interface 3,Device 4,dcim.interface,Interface 3",
"Device 1,dcim.interface,Device 2 Interface,Device 4,dcim.interface,Interface 4",
"Device 1,dcim.interface,Device 3 Interface,Device 4,dcim.interface,Interface 5",
"side_a_site,side_a_device,side_a_type,side_a_name,side_b_site,side_b_device,side_b_type,side_b_name",
"Site 1,Device 3,dcim.interface,Interface 1,Site 2,Device 1,dcim.interface,Interface 1",
"Site 1,Device 3,dcim.interface,Interface 2,Site 2,Device 1,dcim.interface,Interface 2",
"Site 1,Device 3,dcim.interface,Interface 3,Site 2,Device 1,dcim.interface,Interface 3",
"Site 1,Device 1,dcim.interface,Device 2 Interface,Site 2,Device 1,dcim.interface,Interface 4",
"Site 1,Device 1,dcim.interface,Device 3 Interface,Site 2,Device 1,dcim.interface,Interface 5",
)
cls.csv_update_data = (

View File

@@ -185,7 +185,9 @@ class TagViewSet(NetBoxModelViewSet):
class TaggedItemViewSet(RetrieveModelMixin, ListModelMixin, BaseViewSet):
queryset = TaggedItem.objects.prefetch_related('content_type', 'content_object', 'tag')
queryset = TaggedItem.objects.prefetch_related(
'content_type', 'content_object', 'tag'
).order_by('tag__weight', 'tag__name')
serializer_class = serializers.TaggedItemSerializer
filterset_class = filtersets.TaggedItemFilterSet

View File

@@ -21,6 +21,12 @@ WEBHOOK_EVENT_TYPES = {
JOB_ERRORED: 'job_ended',
}
# Jinja environment parameters which support path imports
JINJA_ENV_PARAMS_WITH_PATH_IMPORT = (
'undefined',
'finalize',
)
# Dashboard
DEFAULT_DASHBOARD = [
{

View File

@@ -2,16 +2,17 @@ import importlib.abc
import importlib.util
import os
import sys
from django.core.files.storage import storages
from django.db import models
from django.utils.translation import gettext_lazy as _
from django.http import HttpResponse
from django.utils.module_loading import import_string
from django.utils.translation import gettext_lazy as _
from extras.constants import DEFAULT_MIME_TYPE
from extras.constants import DEFAULT_MIME_TYPE, JINJA_ENV_PARAMS_WITH_PATH_IMPORT
from extras.utils import filename_from_model, filename_from_object
from utilities.jinja2 import render_jinja2
__all__ = (
'PythonModuleMixin',
'RenderTemplateMixin',
@@ -125,12 +126,22 @@ class RenderTemplateMixin(models.Model):
class_name=self.__class__
))
def get_environment_params(self):
"""
Pre-processing of any defined Jinja environment parameters (e.g. to support path resolution).
"""
params = self.environment_params or {}
for name, value in params.items():
if name in JINJA_ENV_PARAMS_WITH_PATH_IMPORT and type(value) is str:
params[name] = import_string(value)
return params
def render(self, context=None, queryset=None):
"""
Render the template with the provided context. The context is passed to the Jinja2 environment as a dictionary.
"""
context = self.get_context(context=context, queryset=queryset)
env_params = self.environment_params or {}
env_params = self.get_environment_params()
output = render_jinja2(self.template_code, context, env_params, getattr(self, 'data_file', None))
# Replace CRLF-style line terminators

View File

@@ -83,3 +83,6 @@ class TaggedItem(GenericTaggedItemBase):
indexes = [models.Index(fields=["content_type", "object_id"])]
verbose_name = _('tagged item')
verbose_name_plural = _('tagged items')
# Note: while there is no ordering applied here (because it would basically be done on fields
# of the related `tag`), there is an ordering applied to extras.api.views.TaggedItemViewSet
# to allow for proper pagination.

View File

@@ -12,3 +12,7 @@ class SerializerNotFound(Exception):
class GraphQLTypeNotFound(Exception):
pass
class QuerySetNotOrdered(Exception):
pass

View File

@@ -1,6 +1,7 @@
from django.db.models import QuerySet
from rest_framework.pagination import LimitOffsetPagination
from netbox.api.exceptions import QuerySetNotOrdered
from netbox.config import get_config
@@ -15,6 +16,12 @@ class OptionalLimitOffsetPagination(LimitOffsetPagination):
def paginate_queryset(self, queryset, request, view=None):
if isinstance(queryset, QuerySet) and not queryset.ordered:
raise QuerySetNotOrdered(
"Paginating over an unordered queryset is unreliable. Ensure that a minimal "
"ordering has been applied to the queryset for this API endpoint."
)
if isinstance(queryset, QuerySet):
self.count = self.get_queryset_count(queryset)
else:

View File

@@ -1,8 +1,13 @@
import uuid
from django.test import RequestFactory, TestCase
from django.urls import reverse
from rest_framework.request import Request
from netbox.api.exceptions import QuerySetNotOrdered
from netbox.api.pagination import OptionalLimitOffsetPagination
from utilities.testing import APITestCase
from users.models import Token
class AppTest(APITestCase):
@@ -26,3 +31,40 @@ class AppTest(APITestCase):
response = self.client.get(f'{url}?format=api', **self.header)
self.assertEqual(response.status_code, 200)
class OptionalLimitOffsetPaginationTest(TestCase):
def setUp(self):
self.paginator = OptionalLimitOffsetPagination()
self.factory = RequestFactory()
def _make_drf_request(self, path='/', query_params=None):
"""Helper to create a proper DRF Request object"""
return Request(self.factory.get(path, query_params or {}))
def test_raises_exception_for_unordered_queryset(self):
"""Should raise QuerySetNotOrdered for unordered QuerySet"""
queryset = Token.objects.all().order_by()
request = self._make_drf_request()
with self.assertRaises(QuerySetNotOrdered) as cm:
self.paginator.paginate_queryset(queryset, request)
error_msg = str(cm.exception)
self.assertIn("Paginating over an unordered queryset is unreliable", error_msg)
self.assertIn("Ensure that a minimal ordering has been applied", error_msg)
def test_allows_ordered_queryset(self):
"""Should not raise exception for ordered QuerySet"""
queryset = Token.objects.all().order_by('created')
request = self._make_drf_request()
self.paginator.paginate_queryset(queryset, request) # Should not raise exception
def test_allows_non_queryset_iterables(self):
"""Should not raise exception for non-QuerySet iterables"""
iterable = [1, 2, 3, 4, 5]
request = self._make_drf_request()
self.paginator.paginate_queryset(iterable, request) # Should not raise exception

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -35,7 +35,7 @@ function showRackElements(
selector: string,
elevation: HTMLObjectElement,
): void {
const elements = elevation.contentDocument?.querySelectorAll(selector) ?? [];
const elements = elevation.querySelectorAll(selector) ?? [];
for (const element of elements) {
element.classList.remove('hidden');
}
@@ -45,7 +45,7 @@ function hideRackElements(
selector: string,
elevation: HTMLObjectElement,
): void {
const elements = elevation.contentDocument?.querySelectorAll(selector) ?? [];
const elements = elevation.querySelectorAll(selector) ?? [];
for (const element of elements) {
element.classList.add('hidden');
}

View File

@@ -19,135 +19,135 @@
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb"
integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==
"@esbuild/aix-ppc64@0.25.6":
version "0.25.6"
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.6.tgz#164b19122e2ed54f85469df9dea98ddb01d5e79e"
integrity sha512-ShbM/3XxwuxjFiuVBHA+d3j5dyac0aEVVq1oluIDf71hUw0aRF59dV/efUsIwFnR6m8JNM2FjZOzmaZ8yG61kw==
"@esbuild/aix-ppc64@0.25.8":
version "0.25.8"
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz#a1414903bb38027382f85f03dda6065056757727"
integrity sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==
"@esbuild/android-arm64@0.25.6":
version "0.25.6"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.25.6.tgz#8f539e7def848f764f6432598e51cc3820fde3a5"
integrity sha512-hd5zdUarsK6strW+3Wxi5qWws+rJhCCbMiC9QZyzoxfk5uHRIE8T287giQxzVpEvCwuJ9Qjg6bEjcRJcgfLqoA==
"@esbuild/android-arm64@0.25.8":
version "0.25.8"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz#c859994089e9767224269884061f89dae6fb51c6"
integrity sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==
"@esbuild/android-arm@0.25.6":
version "0.25.6"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.25.6.tgz#4ceb0f40113e9861169be83e2a670c260dd234ff"
integrity sha512-S8ToEOVfg++AU/bHwdksHNnyLyVM+eMVAOf6yRKFitnwnbwwPNqKr3srzFRe7nzV69RQKb5DgchIX5pt3L53xg==
"@esbuild/android-arm@0.25.8":
version "0.25.8"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.25.8.tgz#96a8f2ca91c6cd29ea90b1af79d83761c8ba0059"
integrity sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==
"@esbuild/android-x64@0.25.6":
version "0.25.6"
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.25.6.tgz#ad4f280057622c25fe985c08999443a195dc63a8"
integrity sha512-0Z7KpHSr3VBIO9A/1wcT3NTy7EB4oNC4upJ5ye3R7taCc2GUdeynSLArnon5G8scPwaU866d3H4BCrE5xLW25A==
"@esbuild/android-x64@0.25.8":
version "0.25.8"
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.25.8.tgz#a3a626c4fec4a024a9fa8c7679c39996e92916f0"
integrity sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==
"@esbuild/darwin-arm64@0.25.6":
version "0.25.6"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.6.tgz#d1f04027396b3d6afc96bacd0d13167dfd9f01f7"
integrity sha512-FFCssz3XBavjxcFxKsGy2DYK5VSvJqa6y5HXljKzhRZ87LvEi13brPrf/wdyl/BbpbMKJNOr1Sd0jtW4Ge1pAA==
"@esbuild/darwin-arm64@0.25.8":
version "0.25.8"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz#a5e1252ca2983d566af1c0ea39aded65736fc66d"
integrity sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==
"@esbuild/darwin-x64@0.25.6":
version "0.25.6"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.25.6.tgz#2b4a6cedb799f635758d7832d75b23772c8ef68f"
integrity sha512-GfXs5kry/TkGM2vKqK2oyiLFygJRqKVhawu3+DOCk7OxLy/6jYkWXhlHwOoTb0WqGnWGAS7sooxbZowy+pK9Yg==
"@esbuild/darwin-x64@0.25.8":
version "0.25.8"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz#5271b0df2bb12ce8df886704bfdd1c7cc01385d2"
integrity sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==
"@esbuild/freebsd-arm64@0.25.6":
version "0.25.6"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.6.tgz#a26266cc97dd78dc3c3f3d6788b1b83697b1055d"
integrity sha512-aoLF2c3OvDn2XDTRvn8hN6DRzVVpDlj2B/F66clWd/FHLiHaG3aVZjxQX2DYphA5y/evbdGvC6Us13tvyt4pWg==
"@esbuild/freebsd-arm64@0.25.8":
version "0.25.8"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz#d0a0e7fdf19733b8bb1566b81df1aa0bb7e46ada"
integrity sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==
"@esbuild/freebsd-x64@0.25.6":
version "0.25.6"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.6.tgz#9feb8e826735c568ebfd94859b22a3fbb6a9bdd2"
integrity sha512-2SkqTjTSo2dYi/jzFbU9Plt1vk0+nNg8YC8rOXXea+iA3hfNJWebKYPs3xnOUf9+ZWhKAaxnQNUf2X9LOpeiMQ==
"@esbuild/freebsd-x64@0.25.8":
version "0.25.8"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz#2de8b2e0899d08f1cb1ef3128e159616e7e85343"
integrity sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==
"@esbuild/linux-arm64@0.25.6":
version "0.25.6"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.25.6.tgz#c07cbed8e249f4c28e7f32781d36fc4695293d28"
integrity sha512-b967hU0gqKd9Drsh/UuAm21Khpoh6mPBSgz8mKRq4P5mVK8bpA+hQzmm/ZwGVULSNBzKdZPQBRT3+WuVavcWsQ==
"@esbuild/linux-arm64@0.25.8":
version "0.25.8"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz#a4209efadc0c2975716458484a4e90c237c48ae9"
integrity sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==
"@esbuild/linux-arm@0.25.6":
version "0.25.6"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.25.6.tgz#d6e2cd8ef3196468065d41f13fa2a61aaa72644a"
integrity sha512-SZHQlzvqv4Du5PrKE2faN0qlbsaW/3QQfUUc6yO2EjFcA83xnwm91UbEEVx4ApZ9Z5oG8Bxz4qPE+HFwtVcfyw==
"@esbuild/linux-arm@0.25.8":
version "0.25.8"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz#ccd9e291c24cd8d9142d819d463e2e7200d25b19"
integrity sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==
"@esbuild/linux-ia32@0.25.6":
version "0.25.6"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.25.6.tgz#3e682bd47c4eddcc4b8f1393dfc8222482f17997"
integrity sha512-aHWdQ2AAltRkLPOsKdi3xv0mZ8fUGPdlKEjIEhxCPm5yKEThcUjHpWB1idN74lfXGnZ5SULQSgtr5Qos5B0bPw==
"@esbuild/linux-ia32@0.25.8":
version "0.25.8"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz#006ad1536d0c2b28fb3a1cf0b53bcb85aaf92c4d"
integrity sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==
"@esbuild/linux-loong64@0.25.6":
version "0.25.6"
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.25.6.tgz#473f5ea2e52399c08ad4cd6b12e6dbcddd630f05"
integrity sha512-VgKCsHdXRSQ7E1+QXGdRPlQ/e08bN6WMQb27/TMfV+vPjjTImuT9PmLXupRlC90S1JeNNW5lzkAEO/McKeJ2yg==
"@esbuild/linux-loong64@0.25.8":
version "0.25.8"
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz#127b3fbfb2c2e08b1397e985932f718f09a8f5c4"
integrity sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==
"@esbuild/linux-mips64el@0.25.6":
version "0.25.6"
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.6.tgz#9960631c9fd61605b0939c19043acf4ef2b51718"
integrity sha512-WViNlpivRKT9/py3kCmkHnn44GkGXVdXfdc4drNmRl15zVQ2+D2uFwdlGh6IuK5AAnGTo2qPB1Djppj+t78rzw==
"@esbuild/linux-mips64el@0.25.8":
version "0.25.8"
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz#837d1449517791e3fa7d82675a2d06d9f56cb340"
integrity sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==
"@esbuild/linux-ppc64@0.25.6":
version "0.25.6"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.6.tgz#477cbf8bb04aa034b94f362c32c86b5c31db8d3e"
integrity sha512-wyYKZ9NTdmAMb5730I38lBqVu6cKl4ZfYXIs31Baf8aoOtB4xSGi3THmDYt4BTFHk7/EcVixkOV2uZfwU3Q2Jw==
"@esbuild/linux-ppc64@0.25.8":
version "0.25.8"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz#aa2e3bd93ab8df084212f1895ca4b03c42d9e0fe"
integrity sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==
"@esbuild/linux-riscv64@0.25.6":
version "0.25.6"
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.6.tgz#bcdb46c8fb8e93aa779e9a0a62cd4ac00dcac626"
integrity sha512-KZh7bAGGcrinEj4qzilJ4hqTY3Dg2U82c8bv+e1xqNqZCrCyc+TL9AUEn5WGKDzm3CfC5RODE/qc96OcbIe33w==
"@esbuild/linux-riscv64@0.25.8":
version "0.25.8"
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz#a340620e31093fef72767dd28ab04214b3442083"
integrity sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==
"@esbuild/linux-s390x@0.25.6":
version "0.25.6"
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.25.6.tgz#f412cf5fdf0aea849ff51c73fd817c6c0234d46d"
integrity sha512-9N1LsTwAuE9oj6lHMyyAM+ucxGiVnEqUdp4v7IaMmrwb06ZTEVCIs3oPPplVsnjPfyjmxwHxHMF8b6vzUVAUGw==
"@esbuild/linux-s390x@0.25.8":
version "0.25.8"
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz#ddfed266c8c13f5efb3105a0cd47f6dcd0e79e71"
integrity sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==
"@esbuild/linux-x64@0.25.6":
version "0.25.6"
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.25.6.tgz#d8233c09b5ebc0c855712dc5eeb835a3a3341108"
integrity sha512-A6bJB41b4lKFWRKNrWoP2LHsjVzNiaurf7wyj/XtFNTsnPuxwEBWHLty+ZE0dWBKuSK1fvKgrKaNjBS7qbFKig==
"@esbuild/linux-x64@0.25.8":
version "0.25.8"
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz#9a4f78c75c051e8c060183ebb39a269ba936a2ac"
integrity sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==
"@esbuild/netbsd-arm64@0.25.6":
version "0.25.6"
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.6.tgz#f51ae8dd1474172e73cf9cbaf8a38d1c72dd8f1a"
integrity sha512-IjA+DcwoVpjEvyxZddDqBY+uJ2Snc6duLpjmkXm/v4xuS3H+3FkLZlDm9ZsAbF9rsfP3zeA0/ArNDORZgrxR/Q==
"@esbuild/netbsd-arm64@0.25.8":
version "0.25.8"
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz#902c80e1d678047926387230bc037e63e00697d0"
integrity sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==
"@esbuild/netbsd-x64@0.25.6":
version "0.25.6"
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.6.tgz#a267538602c0e50a858cf41dcfe5d8036f8da8e7"
integrity sha512-dUXuZr5WenIDlMHdMkvDc1FAu4xdWixTCRgP7RQLBOkkGgwuuzaGSYcOpW4jFxzpzL1ejb8yF620UxAqnBrR9g==
"@esbuild/netbsd-x64@0.25.8":
version "0.25.8"
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz#2d9eb4692add2681ff05a14ce99de54fbed7079c"
integrity sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==
"@esbuild/openbsd-arm64@0.25.6":
version "0.25.6"
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.6.tgz#a51be60c425b85c216479b8c344ad0511635f2d2"
integrity sha512-l8ZCvXP0tbTJ3iaqdNf3pjaOSd5ex/e6/omLIQCVBLmHTlfXW3zAxQ4fnDmPLOB1x9xrcSi/xtCWFwCZRIaEwg==
"@esbuild/openbsd-arm64@0.25.8":
version "0.25.8"
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz#89c3b998c6de739db38ab7fb71a8a76b3fa84a45"
integrity sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==
"@esbuild/openbsd-x64@0.25.6":
version "0.25.6"
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.6.tgz#7e4a743c73f75562e29223ba69d0be6c9c9008da"
integrity sha512-hKrmDa0aOFOr71KQ/19JC7az1P0GWtCN1t2ahYAf4O007DHZt/dW8ym5+CUdJhQ/qkZmI1HAF8KkJbEFtCL7gw==
"@esbuild/openbsd-x64@0.25.8":
version "0.25.8"
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz#2f01615cf472b0e48c077045cfd96b5c149365cc"
integrity sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==
"@esbuild/openharmony-arm64@0.25.6":
version "0.25.6"
resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.6.tgz#2087a5028f387879154ebf44bdedfafa17682e5b"
integrity sha512-+SqBcAWoB1fYKmpWoQP4pGtx+pUUC//RNYhFdbcSA16617cchuryuhOCRpPsjCblKukAckWsV+aQ3UKT/RMPcA==
"@esbuild/openharmony-arm64@0.25.8":
version "0.25.8"
resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz#a201f720cd2c3ebf9a6033fcc3feb069a54b509a"
integrity sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==
"@esbuild/sunos-x64@0.25.6":
version "0.25.6"
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.25.6.tgz#56531f861723ea0dc6283a2bb8837304223cb736"
integrity sha512-dyCGxv1/Br7MiSC42qinGL8KkG4kX0pEsdb0+TKhmJZgCUDBGmyo1/ArCjNGiOLiIAgdbWgmWgib4HoCi5t7kA==
"@esbuild/sunos-x64@0.25.8":
version "0.25.8"
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz#07046c977985a3334667f19e6ab3a01a80862afb"
integrity sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==
"@esbuild/win32-arm64@0.25.6":
version "0.25.6"
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.25.6.tgz#f4989f033deac6fae323acff58764fa8bc01436e"
integrity sha512-42QOgcZeZOvXfsCBJF5Afw73t4veOId//XD3i+/9gSkhSV6Gk3VPlWncctI+JcOyERv85FUo7RxuxGy+z8A43Q==
"@esbuild/win32-arm64@0.25.8":
version "0.25.8"
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz#4a5470caf0d16127c05d4833d4934213c69392d1"
integrity sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==
"@esbuild/win32-ia32@0.25.6":
version "0.25.6"
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.25.6.tgz#b260e9df71e3939eb33925076d39f63cec7d1525"
integrity sha512-4AWhgXmDuYN7rJI6ORB+uU9DHLq/erBbuMoAuB4VWJTu5KtCgcKYPynF0YI1VkBNuEfjNlLrFr9KZPJzrtLkrQ==
"@esbuild/win32-ia32@0.25.8":
version "0.25.8"
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz#3de3e8470b7b328d99dbc3e9ec1eace207e5bbc4"
integrity sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==
"@esbuild/win32-x64@0.25.6":
version "0.25.6"
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.6.tgz#4276edd5c105bc28b11c6a1f76fb9d29d1bd25c1"
integrity sha512-NgJPHHbEpLQgDH2MjQu90pzW/5vvXIZ7KOnPyNBm92A6WgZ/7b6fJyUBjoumLqeOQQGqY2QjQxRo97ah4Sj0cA==
"@esbuild/win32-x64@0.25.8":
version "0.25.8"
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz#610d7ea539d2fcdbe39237b5cc175eb2c4451f9c"
integrity sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==
"@eslint-community/eslint-utils@^4.2.0":
version "4.4.0"
@@ -163,7 +163,12 @@
dependencies:
eslint-visitor-keys "^3.4.3"
"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.6.1":
"@eslint-community/regexpp@^4.10.0":
version "4.12.1"
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0"
integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==
"@eslint-community/regexpp@^4.6.1":
version "4.11.0"
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.11.0.tgz#b0ffd0312b4a3fd2d6f77237e7248a5ad3a680ae"
integrity sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==
@@ -464,10 +469,10 @@
"@parcel/watcher-win32-ia32" "2.4.1"
"@parcel/watcher-win32-x64" "2.4.1"
"@pkgr/core@^0.2.4":
version "0.2.7"
resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.2.7.tgz#eb5014dfd0b03e7f3ba2eeeff506eed89b028058"
integrity sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==
"@pkgr/core@^0.2.9":
version "0.2.9"
resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.2.9.tgz#d229a7b7f9dac167a156992ef23c7f023653f53b"
integrity sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==
"@popperjs/core@^2.11.6", "@popperjs/core@^2.11.8", "@popperjs/core@^2.9.2":
version "2.11.8"
@@ -840,78 +845,78 @@
"@types/estree" "*"
"@typescript-eslint/eslint-plugin@^8.37.0":
version "8.37.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.37.0.tgz#332392883f936137cd6252c8eb236d298e514e70"
integrity sha512-jsuVWeIkb6ggzB+wPCsR4e6loj+rM72ohW6IBn2C+5NCvfUVY8s33iFPySSVXqtm5Hu29Ne/9bnA0JmyLmgenA==
version "8.38.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.38.0.tgz#6e5220d16f2691ab6d983c1737dd5b36e17641b7"
integrity sha512-CPoznzpuAnIOl4nhj4tRr4gIPj5AfKgkiJmGQDaq+fQnRJTYlcBjbX3wbciGmpoPf8DREufuPRe1tNMZnGdanA==
dependencies:
"@eslint-community/regexpp" "^4.10.0"
"@typescript-eslint/scope-manager" "8.37.0"
"@typescript-eslint/type-utils" "8.37.0"
"@typescript-eslint/utils" "8.37.0"
"@typescript-eslint/visitor-keys" "8.37.0"
"@typescript-eslint/scope-manager" "8.38.0"
"@typescript-eslint/type-utils" "8.38.0"
"@typescript-eslint/utils" "8.38.0"
"@typescript-eslint/visitor-keys" "8.38.0"
graphemer "^1.4.0"
ignore "^7.0.0"
natural-compare "^1.4.0"
ts-api-utils "^2.1.0"
"@typescript-eslint/parser@^8.37.0":
version "8.37.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.37.0.tgz#b87f6b61e25ad5cc5bbf8baf809b8da889c89804"
integrity sha512-kVIaQE9vrN9RLCQMQ3iyRlVJpTiDUY6woHGb30JDkfJErqrQEmtdWH3gV0PBAfGZgQXoqzXOO0T3K6ioApbbAA==
version "8.38.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.38.0.tgz#6723a5ea881e1777956b1045cba30be5ea838293"
integrity sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ==
dependencies:
"@typescript-eslint/scope-manager" "8.37.0"
"@typescript-eslint/types" "8.37.0"
"@typescript-eslint/typescript-estree" "8.37.0"
"@typescript-eslint/visitor-keys" "8.37.0"
"@typescript-eslint/scope-manager" "8.38.0"
"@typescript-eslint/types" "8.38.0"
"@typescript-eslint/typescript-estree" "8.38.0"
"@typescript-eslint/visitor-keys" "8.38.0"
debug "^4.3.4"
"@typescript-eslint/project-service@8.37.0":
version "8.37.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.37.0.tgz#0594352e32a4ac9258591b88af77b5653800cdfe"
integrity sha512-BIUXYsbkl5A1aJDdYJCBAo8rCEbAvdquQ8AnLb6z5Lp1u3x5PNgSSx9A/zqYc++Xnr/0DVpls8iQ2cJs/izTXA==
"@typescript-eslint/project-service@8.38.0":
version "8.38.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.38.0.tgz#4900771f943163027fd7d2020a062892056b5e2f"
integrity sha512-dbK7Jvqcb8c9QfH01YB6pORpqX1mn5gDZc9n63Ak/+jD67oWXn3Gs0M6vddAN+eDXBCS5EmNWzbSxsn9SzFWWg==
dependencies:
"@typescript-eslint/tsconfig-utils" "^8.37.0"
"@typescript-eslint/types" "^8.37.0"
"@typescript-eslint/tsconfig-utils" "^8.38.0"
"@typescript-eslint/types" "^8.38.0"
debug "^4.3.4"
"@typescript-eslint/scope-manager@8.37.0":
version "8.37.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.37.0.tgz#a31a3c80ca2ef4ed58de13742debb692e7d4c0a4"
integrity sha512-0vGq0yiU1gbjKob2q691ybTg9JX6ShiVXAAfm2jGf3q0hdP6/BruaFjL/ManAR/lj05AvYCH+5bbVo0VtzmjOA==
"@typescript-eslint/scope-manager@8.38.0":
version "8.38.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.38.0.tgz#5a0efcb5c9cf6e4121b58f87972f567c69529226"
integrity sha512-WJw3AVlFFcdT9Ri1xs/lg8LwDqgekWXWhH3iAF+1ZM+QPd7oxQ6jvtW/JPwzAScxitILUIFs0/AnQ/UWHzbATQ==
dependencies:
"@typescript-eslint/types" "8.37.0"
"@typescript-eslint/visitor-keys" "8.37.0"
"@typescript-eslint/types" "8.38.0"
"@typescript-eslint/visitor-keys" "8.38.0"
"@typescript-eslint/tsconfig-utils@8.37.0", "@typescript-eslint/tsconfig-utils@^8.37.0":
version "8.37.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.37.0.tgz#47a2760d265c6125f8e7864bc5c8537cad2bd053"
integrity sha512-1/YHvAVTimMM9mmlPvTec9NP4bobA1RkDbMydxG8omqwJJLEW/Iy2C4adsAESIXU3WGLXFHSZUU+C9EoFWl4Zg==
"@typescript-eslint/tsconfig-utils@8.38.0", "@typescript-eslint/tsconfig-utils@^8.38.0":
version "8.38.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.38.0.tgz#6de4ce224a779601a8df667db56527255c42c4d0"
integrity sha512-Lum9RtSE3EroKk/bYns+sPOodqb2Fv50XOl/gMviMKNvanETUuUcC9ObRbzrJ4VSd2JalPqgSAavwrPiPvnAiQ==
"@typescript-eslint/type-utils@8.37.0":
version "8.37.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.37.0.tgz#2a682e4c6ff5886712dad57e9787b5e417124507"
integrity sha512-SPkXWIkVZxhgwSwVq9rqj/4VFo7MnWwVaRNznfQDc/xPYHjXnPfLWn+4L6FF1cAz6e7dsqBeMawgl7QjUMj4Ow==
"@typescript-eslint/type-utils@8.38.0":
version "8.38.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.38.0.tgz#a56cd84765fa6ec135fe252b5db61e304403a85b"
integrity sha512-c7jAvGEZVf0ao2z+nnz8BUaHZD09Agbh+DY7qvBQqLiz8uJzRgVPj5YvOh8I8uEiH8oIUGIfHzMwUcGVco/SJg==
dependencies:
"@typescript-eslint/types" "8.37.0"
"@typescript-eslint/typescript-estree" "8.37.0"
"@typescript-eslint/utils" "8.37.0"
"@typescript-eslint/types" "8.38.0"
"@typescript-eslint/typescript-estree" "8.38.0"
"@typescript-eslint/utils" "8.38.0"
debug "^4.3.4"
ts-api-utils "^2.1.0"
"@typescript-eslint/types@8.37.0", "@typescript-eslint/types@^8.37.0":
version "8.37.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.37.0.tgz#09517aa9625eb3c68941dde3ac8835740587b6ff"
integrity sha512-ax0nv7PUF9NOVPs+lmQ7yIE7IQmAf8LGcXbMvHX5Gm+YJUYNAl340XkGnrimxZ0elXyoQJuN5sbg6C4evKA4SQ==
"@typescript-eslint/types@8.38.0", "@typescript-eslint/types@^8.38.0":
version "8.38.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.38.0.tgz#297351c994976b93c82ac0f0e206c8143aa82529"
integrity sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw==
"@typescript-eslint/typescript-estree@8.37.0":
version "8.37.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.37.0.tgz#a07e4574d8e6e4355a558f61323730c987f5fcbc"
integrity sha512-zuWDMDuzMRbQOM+bHyU4/slw27bAUEcKSKKs3hcv2aNnc/tvE/h7w60dwVw8vnal2Pub6RT1T7BI8tFZ1fE+yg==
"@typescript-eslint/typescript-estree@8.38.0":
version "8.38.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.38.0.tgz#82262199eb6778bba28a319e25ad05b1158957df"
integrity sha512-fooELKcAKzxux6fA6pxOflpNS0jc+nOQEEOipXFNjSlBS6fqrJOVY/whSn70SScHrcJ2LDsxWrneFoWYSVfqhQ==
dependencies:
"@typescript-eslint/project-service" "8.37.0"
"@typescript-eslint/tsconfig-utils" "8.37.0"
"@typescript-eslint/types" "8.37.0"
"@typescript-eslint/visitor-keys" "8.37.0"
"@typescript-eslint/project-service" "8.38.0"
"@typescript-eslint/tsconfig-utils" "8.38.0"
"@typescript-eslint/types" "8.38.0"
"@typescript-eslint/visitor-keys" "8.38.0"
debug "^4.3.4"
fast-glob "^3.3.2"
is-glob "^4.0.3"
@@ -919,22 +924,22 @@
semver "^7.6.0"
ts-api-utils "^2.1.0"
"@typescript-eslint/utils@8.37.0":
version "8.37.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.37.0.tgz#189ea59b2709f5d898614611f091a776751ee335"
integrity sha512-TSFvkIW6gGjN2p6zbXo20FzCABbyUAuq6tBvNRGsKdsSQ6a7rnV6ADfZ7f4iI3lIiXc4F4WWvtUfDw9CJ9pO5A==
"@typescript-eslint/utils@8.38.0":
version "8.38.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.38.0.tgz#5f10159899d30eb92ba70e642ca6f754bddbf15a"
integrity sha512-hHcMA86Hgt+ijJlrD8fX0j1j8w4C92zue/8LOPAFioIno+W0+L7KqE8QZKCcPGc/92Vs9x36w/4MPTJhqXdyvg==
dependencies:
"@eslint-community/eslint-utils" "^4.7.0"
"@typescript-eslint/scope-manager" "8.37.0"
"@typescript-eslint/types" "8.37.0"
"@typescript-eslint/typescript-estree" "8.37.0"
"@typescript-eslint/scope-manager" "8.38.0"
"@typescript-eslint/types" "8.38.0"
"@typescript-eslint/typescript-estree" "8.38.0"
"@typescript-eslint/visitor-keys@8.37.0":
version "8.37.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.37.0.tgz#cdb6a6bd3e8d6dd69bd70c1bdda36e2d18737455"
integrity sha512-YzfhzcTnZVPiLfP/oeKtDp2evwvHLMe0LOy7oe+hb9KKIumLNohYS9Hgp1ifwpu42YWxhZE8yieggz6JpqO/1w==
"@typescript-eslint/visitor-keys@8.38.0":
version "8.38.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.38.0.tgz#a9765a527b082cb8fc60fd8a16e47c7ad5b60ea5"
integrity sha512-pWrTcoFNWuwHlA9CvlfSsGWs14JxfN1TH25zM5L7o0pRLhsoZkDnTsXfQRJBEWJoV5DL0jf+Z+sxiud+K0mq1g==
dependencies:
"@typescript-eslint/types" "8.37.0"
"@typescript-eslint/types" "8.38.0"
eslint-visitor-keys "^4.2.1"
"@ungap/structured-clone@^1.2.0":
@@ -1120,9 +1125,9 @@ brace-expansion@^1.1.7:
concat-map "0.0.1"
brace-expansion@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae"
integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==
version "2.0.2"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.2.tgz#54fc53237a613d854c7bd37463aad17df87214e7"
integrity sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==
dependencies:
balanced-match "^1.0.0"
@@ -1344,11 +1349,11 @@ debug@^4.3.1, debug@^4.3.2, debug@^4.3.5:
ms "2.1.2"
debug@^4.3.4:
version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
version "4.4.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b"
integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==
dependencies:
ms "2.1.2"
ms "^2.1.3"
decode-uri-component@^0.4.1:
version "0.4.1"
@@ -1633,36 +1638,36 @@ esbuild-sass-plugin@^3.3.1:
sass "^1.71.1"
esbuild@^0.25.6:
version "0.25.6"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.25.6.tgz#9b82a3db2fa131aec069ab040fd57ed0a880cdcd"
integrity sha512-GVuzuUwtdsghE3ocJ9Bs8PNoF13HNQ5TXbEi2AhvVb8xU1Iwt9Fos9FEamfoee+u/TOsn7GUWc04lz46n2bbTg==
version "0.25.8"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.25.8.tgz#482d42198b427c9c2f3a81b63d7663aecb1dda07"
integrity sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==
optionalDependencies:
"@esbuild/aix-ppc64" "0.25.6"
"@esbuild/android-arm" "0.25.6"
"@esbuild/android-arm64" "0.25.6"
"@esbuild/android-x64" "0.25.6"
"@esbuild/darwin-arm64" "0.25.6"
"@esbuild/darwin-x64" "0.25.6"
"@esbuild/freebsd-arm64" "0.25.6"
"@esbuild/freebsd-x64" "0.25.6"
"@esbuild/linux-arm" "0.25.6"
"@esbuild/linux-arm64" "0.25.6"
"@esbuild/linux-ia32" "0.25.6"
"@esbuild/linux-loong64" "0.25.6"
"@esbuild/linux-mips64el" "0.25.6"
"@esbuild/linux-ppc64" "0.25.6"
"@esbuild/linux-riscv64" "0.25.6"
"@esbuild/linux-s390x" "0.25.6"
"@esbuild/linux-x64" "0.25.6"
"@esbuild/netbsd-arm64" "0.25.6"
"@esbuild/netbsd-x64" "0.25.6"
"@esbuild/openbsd-arm64" "0.25.6"
"@esbuild/openbsd-x64" "0.25.6"
"@esbuild/openharmony-arm64" "0.25.6"
"@esbuild/sunos-x64" "0.25.6"
"@esbuild/win32-arm64" "0.25.6"
"@esbuild/win32-ia32" "0.25.6"
"@esbuild/win32-x64" "0.25.6"
"@esbuild/aix-ppc64" "0.25.8"
"@esbuild/android-arm" "0.25.8"
"@esbuild/android-arm64" "0.25.8"
"@esbuild/android-x64" "0.25.8"
"@esbuild/darwin-arm64" "0.25.8"
"@esbuild/darwin-x64" "0.25.8"
"@esbuild/freebsd-arm64" "0.25.8"
"@esbuild/freebsd-x64" "0.25.8"
"@esbuild/linux-arm" "0.25.8"
"@esbuild/linux-arm64" "0.25.8"
"@esbuild/linux-ia32" "0.25.8"
"@esbuild/linux-loong64" "0.25.8"
"@esbuild/linux-mips64el" "0.25.8"
"@esbuild/linux-ppc64" "0.25.8"
"@esbuild/linux-riscv64" "0.25.8"
"@esbuild/linux-s390x" "0.25.8"
"@esbuild/linux-x64" "0.25.8"
"@esbuild/netbsd-arm64" "0.25.8"
"@esbuild/netbsd-x64" "0.25.8"
"@esbuild/openbsd-arm64" "0.25.8"
"@esbuild/openbsd-x64" "0.25.8"
"@esbuild/openharmony-arm64" "0.25.8"
"@esbuild/sunos-x64" "0.25.8"
"@esbuild/win32-arm64" "0.25.8"
"@esbuild/win32-ia32" "0.25.8"
"@esbuild/win32-x64" "0.25.8"
escape-string-regexp@^4.0.0:
version "4.0.0"
@@ -1737,9 +1742,9 @@ eslint-plugin-import@^2.32.0:
tsconfig-paths "^3.15.0"
eslint-plugin-prettier@^5.5.1:
version "5.5.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.1.tgz#470820964de9aedb37e9ce62c3266d2d26d08d15"
integrity sha512-dobTkHT6XaEVOo8IO90Q4DOSxnm3Y151QxPJlM/vKC0bVy+d6cVWQZLlFiuZPP0wS6vZwSKeJgKkcS+KfMBlRw==
version "5.5.3"
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.3.tgz#1f88e9220a72ac8be171eec5f9d4e4d529b5f4a0"
integrity sha512-NAdMYww51ehKfDyDhv59/eIItUVzU0Io9H2E8nHNGKEeeqlnci+1gCvrHib6EmZdf6GxF+LCV5K7UC65Ezvw7w==
dependencies:
prettier-linter-helpers "^1.0.0"
synckit "^0.11.7"
@@ -1850,15 +1855,15 @@ fast-diff@^1.1.2:
integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==
fast-glob@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129"
integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==
version "3.3.3"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818"
integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==
dependencies:
"@nodelib/fs.stat" "^2.0.2"
"@nodelib/fs.walk" "^1.2.3"
glob-parent "^5.1.2"
merge2 "^1.3.0"
micromatch "^4.0.4"
micromatch "^4.0.8"
fast-json-stable-stringify@^2.0.0:
version "2.1.0"
@@ -1871,9 +1876,9 @@ fast-levenshtein@^2.0.6:
integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
fastq@^1.6.0:
version "1.17.1"
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47"
integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==
version "1.19.1"
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.19.1.tgz#d50eaba803c8846a883c16492821ebcd2cda55f5"
integrity sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==
dependencies:
reusify "^1.0.4"
@@ -2741,7 +2746,7 @@ meros@^1.1.4:
resolved "https://registry.yarnpkg.com/meros/-/meros-1.3.0.tgz#c617d2092739d55286bf618129280f362e6242f2"
integrity sha512-2BNGOimxEz5hmjUG2FwoxCt5HN7BXdaWyFqEwxPTrJzVdABtrL4TiHTcsWSFAxPQ/tOnEaQEJh3qWq71QRMY+w==
micromatch@^4.0.4, micromatch@^4.0.5:
micromatch@^4.0.5, micromatch@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202"
integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==
@@ -2773,7 +2778,7 @@ ms@2.1.2:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
ms@^2.1.1:
ms@^2.1.1, ms@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
@@ -3110,9 +3115,9 @@ resolve@^1.22.4, resolve@^1.22.8:
supports-preserve-symlinks-flag "^1.0.0"
reusify@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
version "1.1.0"
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f"
integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==
rimraf@^3.0.2:
version "3.0.2"
@@ -3217,7 +3222,12 @@ semver@^6.3.1:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
semver@^7.6.0, semver@^7.6.3:
semver@^7.6.0:
version "7.7.2"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58"
integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==
semver@^7.6.3:
version "7.6.3"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143"
integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
@@ -3430,11 +3440,11 @@ supports-preserve-symlinks-flag@^1.0.0:
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
synckit@^0.11.7:
version "0.11.8"
resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.11.8.tgz#b2aaae998a4ef47ded60773ad06e7cb821f55457"
integrity sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==
version "0.11.11"
resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.11.11.tgz#c0b619cf258a97faa209155d9cd1699b5c998cb0"
integrity sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==
dependencies:
"@pkgr/core" "^0.2.4"
"@pkgr/core" "^0.2.9"
tapable@^2.2.0:
version "2.2.1"

View File

@@ -1,3 +1,3 @@
version: "4.3.4"
version: "4.3.5"
edition: "Community"
published: "2025-07-15"
published: "2025-07-29"

View File

@@ -55,7 +55,7 @@ Blocks:
{# Release info #}
<div class="text-muted text-center fs-5 my-3">
{{ settings.RELEASE.name }}
{% if not settings.RELEASE.features.commercial %}
{% if not settings.RELEASE.features.commercial and not settings.ISOLATED_DEPLOYMENT %}
<div>
<a href="https://netboxlabs.com/netbox-cloud/" class="text-muted">{% trans "Get" %} Cloud</a> |
<a href="https://netboxlabs.com/netbox-enterprise/" class="text-muted">{% trans "Get" %} Enterprise</a>
@@ -184,7 +184,7 @@ Blocks:
{% endif %}
{# Commercial links #}
{% if settings.RELEASE.features.commercial %}
{% if settings.RELEASE.features.commercial and not settings.ISOLATED_DEPLOYMENT %}
{# LinkedIn #}
<li class="list-inline-item">
<a href="https://www.linkedin.com/company/netboxlabs/" target="_blank" class="link-secondary" rel="noopener" aria-label="LinkedIn">
@@ -199,7 +199,7 @@ Blocks:
</li>
{# Community links #}
{% else %}
{% elif not settings.ISOLATED_DEPLOYMENT %}
{# GitHub #}
<li class="list-inline-item">
<a href="https://github.com/netbox-community/netbox" target="_blank" class="link-secondary" rel="noopener" aria-label="{% trans "Source Code" %}">

View File

@@ -1,5 +1,5 @@
{% load i18n %}
<div style="margin-left: -30px">
<div style="margin-left: -30px" class="rack_elevation">
<div
hx-get="{% url 'dcim-api:rack-elevation' pk=object.pk %}?face={{ face }}&render=svg{% if extra_params %}&{{ extra_params }}{% endif %}"
hx-trigger="intersect"

View File

@@ -45,12 +45,17 @@ class TenantBulkEditForm(NetBoxModelBulkEditForm):
queryset=TenantGroup.objects.all(),
required=False
)
description = forms.CharField(
label=_('Description'),
max_length=200,
required=False
)
model = Tenant
fieldsets = (
FieldSet('group'),
FieldSet('group', 'description'),
)
nullable_fields = ('group',)
nullable_fields = ('group', 'description')
#

View File

@@ -98,6 +98,7 @@ class TenantTestCase(ViewTestCases.PrimaryObjectViewTestCase):
cls.bulk_edit_data = {
'group': tenant_groups[1].pk,
'description': 'Bulk edit description',
}

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,17 @@
# Generated by Django 5.2.4 on 2025-07-23 17:28
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('users', '0009_update_group_perms'),
]
operations = [
migrations.AlterModelOptions(
name='token',
options={'ordering': ('-created',)},
),
]

View File

@@ -74,6 +74,7 @@ class Token(models.Model):
class Meta:
verbose_name = _('token')
verbose_name_plural = _('tokens')
ordering = ('-created',)
def __str__(self):
return self.key if settings.ALLOW_TOKEN_RETRIEVAL else self.partial

View File

@@ -13,6 +13,8 @@ FILTER_CHAR_BASED_LOOKUP_MAP = dict(
ie='iexact',
nie='iexact',
empty='empty',
regex='regex',
iregex='iregex',
)
FILTER_NUMERIC_BASED_LOOKUP_MAP = dict(

View File

@@ -67,5 +67,8 @@ def reapply_model_ordering(queryset: QuerySet) -> QuerySet:
# MPTT-based models are exempt from this; use caution when annotating querysets of these models
if any(isinstance(manager, TreeManager) for manager in queryset.model._meta.local_managers):
return queryset
elif queryset.ordered:
return queryset
ordering = queryset.model._meta.ordering
return queryset.order_by(*ordering)

View File

@@ -180,6 +180,10 @@ class BaseFilterSetTest(TestCase):
self.assertEqual(self.filters['charfield__niew'].exclude, True)
self.assertEqual(self.filters['charfield__empty'].lookup_expr, 'empty')
self.assertEqual(self.filters['charfield__empty'].exclude, False)
self.assertEqual(self.filters['charfield__regex'].lookup_expr, 'regex')
self.assertEqual(self.filters['charfield__regex'].exclude, False)
self.assertEqual(self.filters['charfield__iregex'].lookup_expr, 'iregex')
self.assertEqual(self.filters['charfield__iregex'].exclude, False)
def test_number_filter(self):
self.assertIsInstance(self.filters['numberfield'], django_filters.NumberFilter)
@@ -220,6 +224,10 @@ class BaseFilterSetTest(TestCase):
self.assertEqual(self.filters['macaddressfield__iew'].exclude, False)
self.assertEqual(self.filters['macaddressfield__niew'].lookup_expr, 'iendswith')
self.assertEqual(self.filters['macaddressfield__niew'].exclude, True)
self.assertEqual(self.filters['macaddressfield__regex'].lookup_expr, 'regex')
self.assertEqual(self.filters['macaddressfield__regex'].exclude, False)
self.assertEqual(self.filters['macaddressfield__iregex'].lookup_expr, 'iregex')
self.assertEqual(self.filters['macaddressfield__iregex'].exclude, False)
def test_model_choice_filter(self):
self.assertIsInstance(self.filters['modelchoicefield'], django_filters.ModelChoiceFilter)
@@ -257,6 +265,10 @@ class BaseFilterSetTest(TestCase):
self.assertEqual(self.filters['multivaluecharfield__iew'].exclude, False)
self.assertEqual(self.filters['multivaluecharfield__niew'].lookup_expr, 'iendswith')
self.assertEqual(self.filters['multivaluecharfield__niew'].exclude, True)
self.assertEqual(self.filters['multivaluecharfield__regex'].lookup_expr, 'regex')
self.assertEqual(self.filters['multivaluecharfield__regex'].exclude, False)
self.assertEqual(self.filters['multivaluecharfield__iregex'].lookup_expr, 'iregex')
self.assertEqual(self.filters['multivaluecharfield__iregex'].exclude, False)
def test_multi_value_date_filter(self):
self.assertIsInstance(self.filters['datefield'], MultiValueDateFilter)
@@ -340,6 +352,10 @@ class BaseFilterSetTest(TestCase):
self.assertEqual(self.filters['multiplechoicefield__iew'].exclude, False)
self.assertEqual(self.filters['multiplechoicefield__niew'].lookup_expr, 'iendswith')
self.assertEqual(self.filters['multiplechoicefield__niew'].exclude, True)
self.assertEqual(self.filters['multiplechoicefield__regex'].lookup_expr, 'regex')
self.assertEqual(self.filters['multiplechoicefield__regex'].exclude, False)
self.assertEqual(self.filters['multiplechoicefield__iregex'].lookup_expr, 'iregex')
self.assertEqual(self.filters['multiplechoicefield__iregex'].exclude, False)
def test_tag_filter(self):
self.assertIsInstance(self.filters['tagfield'], TagFilter)
@@ -534,6 +550,14 @@ class DynamicFilterLookupExpressionTest(TestCase):
params = {'slug__niew': ['-1']}
self.assertEqual(SiteFilterSet(params, Site.objects.all()).qs.count(), 2)
def test_site_slug_regex(self):
params = {'slug__regex': ['^def-[a-z]*-2$']}
self.assertEqual(SiteFilterSet(params, Site.objects.all()).qs.count(), 1)
def test_site_slug_iregex(self):
params = {'slug__iregex': ['^DEF-[a-z]*-2$']}
self.assertEqual(SiteFilterSet(params, Site.objects.all()).qs.count(), 1)
def test_provider_asn_lt(self):
params = {'asn__lt': [65101]}
self.assertEqual(ASNFilterSet(params, ASN.objects.all()).qs.count(), 1)
@@ -618,6 +642,14 @@ class DynamicFilterLookupExpressionTest(TestCase):
params = {'mac_address__nic': ['aa:', 'bb']}
self.assertEqual(DeviceFilterSet(params, Device.objects.all()).qs.count(), 1)
def test_device_mac_address_regex(self):
params = {'mac_address__regex': ['^cc.*:03$']}
self.assertEqual(DeviceFilterSet(params, Device.objects.all()).qs.count(), 1)
def test_device_mac_address_iregex(self):
params = {'mac_address__iregex': ['^CC.*:03$']}
self.assertEqual(DeviceFilterSet(params, Device.objects.all()).qs.count(), 1)
def test_interface_rf_role_empty(self):
params = {'rf_role__empty': 'true'}
self.assertEqual(InterfaceFilterSet(params, Interface.objects.all()).qs.count(), 5)

View File

@@ -3,7 +3,7 @@
[project]
name = "netbox"
version = "4.3.4"
version = "4.3.5"
requires-python = ">=3.10"
authors = [
{ name = "NetBox Community" }

View File

@@ -20,21 +20,21 @@ drf-spectacular-sidecar==2025.7.1
feedparser==6.0.11
gunicorn==23.0.0
Jinja2==3.1.6
jsonschema==4.24.0
jsonschema==4.25.0
Markdown==3.8.2
mkdocs-material==9.6.15
mkdocstrings[python]==0.29.1
mkdocs-material==9.6.16
mkdocstrings[python]==0.30.0
netaddr==1.3.0
nh3==0.2.22
nh3==0.3.0
Pillow==11.3.0
psycopg[c,pool]==3.2.9
PyYAML==6.0.2
requests==2.32.4
rq==2.4.0
rq==2.4.1
social-auth-app-django==5.5.1
social-auth-core==4.7.0
strawberry-graphql==0.276.0
strawberry-graphql-django==0.60.0
strawberry-graphql==0.278.0
strawberry-graphql-django==0.65.1
svgwrite==1.4.3
tablib==3.8.0
tzdata==2025.2