Skip to content

Commit 0af1dbf

Browse files
Merge branch 'feature' into 19523-add-instance-filter-for-devicetype-and-moduletype-countcachefield
2 parents 4e8f376 + a4365be commit 0af1dbf

File tree

156 files changed

+11077
-8817
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

156 files changed

+11077
-8817
lines changed

.github/ISSUE_TEMPLATE/01-feature_request.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ body:
1515
attributes:
1616
label: NetBox version
1717
description: What version of NetBox are you currently running?
18-
placeholder: v4.4.5
18+
placeholder: v4.4.6
1919
validations:
2020
required: true
2121
- type: dropdown

.github/ISSUE_TEMPLATE/02-bug_report.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ body:
2727
attributes:
2828
label: NetBox Version
2929
description: What version of NetBox are you currently running?
30-
placeholder: v4.4.5
30+
placeholder: v4.4.6
3131
validations:
3232
required: true
3333
- type: dropdown

.pre-commit-config.yaml

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,6 @@ repos:
2121
language: system
2222
pass_filenames: false
2323
types: [python]
24-
- id: openapi-check
25-
name: "Validate OpenAPI schema"
26-
description: "Check for any unexpected changes to the OpenAPI schema"
27-
files: api/.*\.py$
28-
entry: scripts/verify-openapi.sh
29-
language: system
30-
pass_filenames: false
31-
types: [python]
3224
- id: mkdocs-build
3325
name: "Build documentation"
3426
description: "Build the documentation with mkdocs"

contrib/openapi.json

Lines changed: 140 additions & 70 deletions
Large diffs are not rendered by default.

docs/configuration/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ Some configuration parameters are primarily controlled via NetBox's admin interf
3535
* [`POWERFEED_DEFAULT_MAX_UTILIZATION`](./default-values.md#powerfeed_default_max_utilization)
3636
* [`POWERFEED_DEFAULT_VOLTAGE`](./default-values.md#powerfeed_default_voltage)
3737
* [`PREFER_IPV4`](./miscellaneous.md#prefer_ipv4)
38+
* [`PROTECTION_RULES`](./data-validation.md#protection_rules)
3839
* [`RACK_ELEVATION_DEFAULT_UNIT_HEIGHT`](./default-values.md#rack_elevation_default_unit_height)
3940
* [`RACK_ELEVATION_DEFAULT_UNIT_WIDTH`](./default-values.md#rack_elevation_default_unit_width)
4041

docs/configuration/security.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ If `True`, the cookie employed for cross-site request forgery (CSRF) protection
8181

8282
Default: `[]`
8383

84-
Defines a list of trusted origins for unsafe (e.g. `POST`) requests. This is a pass-through to Django's [`CSRF_TRUSTED_ORIGINS`](https://docs.djangoproject.com/en/stable/ref/settings/#csrf-trusted-origins) setting. Note that each host listed must specify a scheme (e.g. `http://` or `https://).
84+
Defines a list of trusted origins for unsafe (e.g. `POST`) requests. This is a pass-through to Django's [`CSRF_TRUSTED_ORIGINS`](https://docs.djangoproject.com/en/stable/ref/settings/#csrf-trusted-origins) setting. Note that each host listed must specify a scheme (e.g. `http://` or `https://`).
8585

8686
```python
8787
CSRF_TRUSTED_ORIGINS = (
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
# UI Components
2+
3+
!!! note "New in NetBox v4.5"
4+
All UI components described here were introduced in NetBox v4.5. Be sure to set the minimum NetBox version to 4.5.0 for your plugin before incorporating any of these resources.
5+
6+
!!! danger "Beta Feature"
7+
UI components are considered a beta feature, and are still under active development. Please be aware that the API for resources on this page is subject to change in future releases.
8+
9+
To simply the process of designing your plugin's user interface, and to encourage a consistent look and feel throughout the entire application, NetBox provides a set of components that enable programmatic UI design. These make it possible to declare complex page layouts with little or no custom HTML.
10+
11+
## Page Layout
12+
13+
A layout defines the general arrangement of content on a page into rows and columns. The layout is defined under the [view](./views.md) and declares a set of rows, each of which may have one or more columns. Below is an example layout.
14+
15+
```
16+
+-------+-------+-------+
17+
| Col 1 | Col 2 | Col 3 |
18+
+-------+-------+-------+
19+
| Col 4 |
20+
+-----------+-----------+
21+
| Col 5 | Col 6 |
22+
+-----------+-----------+
23+
```
24+
25+
The above layout can be achieved with the following declaration under a view:
26+
27+
```python
28+
from netbox.ui import layout
29+
from netbox.views import generic
30+
31+
class MyView(generic.ObjectView):
32+
layout = layout.Layout(
33+
layout.Row(
34+
layout.Column(),
35+
layout.Column(),
36+
layout.Column(),
37+
),
38+
layout.Row(
39+
layout.Column(),
40+
),
41+
layout.Row(
42+
layout.Column(),
43+
layout.Column(),
44+
),
45+
)
46+
```
47+
48+
!!! note
49+
Currently, layouts are supported only for subclasses of [`generic.ObjectView`](./views.md#netbox.views.generic.ObjectView).
50+
51+
::: netbox.ui.layout.Layout
52+
53+
::: netbox.ui.layout.SimpleLayout
54+
55+
::: netbox.ui.layout.Row
56+
57+
::: netbox.ui.layout.Column
58+
59+
## Panels
60+
61+
Within each column, related blocks of content are arranged into panels. Each panel has a title and may have a set of associated actions, but the content within is otherwise arbitrary.
62+
63+
Plugins can define their own panels by inheriting from the base class `netbox.ui.panels.Panel`. Override the `get_context()` method to pass additional context to your custom panel template. An example is provided below.
64+
65+
```python
66+
from django.utils.translation import gettext_lazy as _
67+
from netbox.ui.panels import Panel
68+
69+
class RecentChangesPanel(Panel):
70+
template_name = 'my_plugin/panels/recent_changes.html'
71+
title = _('Recent Changes')
72+
73+
def get_context(self, context):
74+
return {
75+
**super().get_context(context),
76+
'changes': get_changes()[:10],
77+
}
78+
```
79+
80+
NetBox also includes a set of panels suite for specific uses, such as display object details or embedding a table of related objects. These are listed below.
81+
82+
::: netbox.ui.panels.Panel
83+
84+
::: netbox.ui.panels.ObjectPanel
85+
86+
::: netbox.ui.panels.ObjectAttributesPanel
87+
88+
#### Object Attributes
89+
90+
The following classes are available to represent object attributes within an ObjectAttributesPanel. Additionally, plugins can subclass `netbox.ui.attrs.ObjectAttribute` to create custom classes.
91+
92+
| Class | Description |
93+
|--------------------------------------|--------------------------------------------------|
94+
| `netbox.ui.attrs.AddressAttr` | A physical or mailing address. |
95+
| `netbox.ui.attrs.BooleanAttr` | A boolean value |
96+
| `netbox.ui.attrs.ColorAttr` | A color expressed in RGB |
97+
| `netbox.ui.attrs.ChoiceAttr` | A selection from a set of choices |
98+
| `netbox.ui.attrs.GPSCoordinatesAttr` | GPS coordinates (latitude and longitude) |
99+
| `netbox.ui.attrs.ImageAttr` | An attached image (displays the image) |
100+
| `netbox.ui.attrs.NestedObjectAttr` | A related nested object |
101+
| `netbox.ui.attrs.NumericAttr` | An integer or float value |
102+
| `netbox.ui.attrs.RelatedObjectAttr` | A related object |
103+
| `netbox.ui.attrs.TemplatedAttr` | Renders an attribute using a custom template |
104+
| `netbox.ui.attrs.TextAttr` | A string (text) value |
105+
| `netbox.ui.attrs.TimezoneAttr` | A timezone with annotated offset |
106+
| `netbox.ui.attrs.UtilizationAttr` | A numeric value expressed as a utilization graph |
107+
108+
::: netbox.ui.panels.OrganizationalObjectPanel
109+
110+
::: netbox.ui.panels.NestedGroupObjectPanel
111+
112+
::: netbox.ui.panels.CommentsPanel
113+
114+
::: netbox.ui.panels.JSONPanel
115+
116+
::: netbox.ui.panels.RelatedObjectsPanel
117+
118+
::: netbox.ui.panels.ObjectsTablePanel
119+
120+
::: netbox.ui.panels.TemplatePanel
121+
122+
::: netbox.ui.panels.PluginContentPanel
123+
124+
## Panel Actions
125+
126+
Each panel may have actions associated with it. These render as links or buttons within the panel header, opposite the panel's title. For example, a common use case is to include an "Add" action on a panel which displays a list of objects. Below is an example of this.
127+
128+
```python
129+
from django.utils.translation import gettext_lazy as _
130+
from netbox.ui import actions, panels
131+
132+
panels.ObjectsTablePanel(
133+
model='dcim.Region',
134+
title=_('Child Regions'),
135+
filters={'parent_id': lambda ctx: ctx['object'].pk},
136+
actions=[
137+
actions.AddObject('dcim.Region', url_params={'parent': lambda ctx: ctx['object'].pk}),
138+
],
139+
),
140+
```
141+
142+
::: netbox.ui.actions.PanelAction
143+
144+
::: netbox.ui.actions.LinkAction
145+
146+
::: netbox.ui.actions.AddObject
147+
148+
::: netbox.ui.actions.CopyContent

docs/release-notes/version-4.4.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,33 @@
11
# NetBox v4.4
22

3+
## v4.4.6 (2025-11-11)
4+
5+
### Enhancements
6+
7+
* [#14171](https://github.com/netbox-community/netbox/issues/14171) - Support VLAN assignment for device & VM interfaces being bulk imported
8+
* [#20297](https://github.com/netbox-community/netbox/issues/20297) - Introduce additional coaxial cable types
9+
10+
### Bug Fixes
11+
12+
* [#20378](https://github.com/netbox-community/netbox/issues/20378) - Prevent exception when attempting to delete a data source utilized by a custom script
13+
* [#20645](https://github.com/netbox-community/netbox/issues/20645) - CSVChoiceField should defer to model field's default value when CSV field is empty
14+
* [#20647](https://github.com/netbox-community/netbox/issues/20647) - Improve handling of empty strings during bulk imports
15+
* [#20653](https://github.com/netbox-community/netbox/issues/20653) - Fix filtering of jobs by object type ID
16+
* [#20660](https://github.com/netbox-community/netbox/issues/20660) - Optimize loading of custom script modules from remote storage
17+
* [#20670](https://github.com/netbox-community/netbox/issues/20670) - Improve validation of related objects during bulk import
18+
* [#20688](https://github.com/netbox-community/netbox/issues/20688) - Suppress non-harmful "No active configuration revision found" warning message
19+
* [#20697](https://github.com/netbox-community/netbox/issues/20697) - Prevent duplication of signals which increment/decrement related object counts
20+
* [#20699](https://github.com/netbox-community/netbox/issues/20699) - Ensure proper ordering of changelog entries resulting from cascading deletions
21+
* [#20713](https://github.com/netbox-community/netbox/issues/20713) - Ensure a pre-change snapshot is recorded on virtual chassis members being added/removed
22+
* [#20721](https://github.com/netbox-community/netbox/issues/20721) - Fix breadcrumb navigation links in UI for background tasks
23+
* [#20738](https://github.com/netbox-community/netbox/issues/20738) - Deleting a virtual chassis should nullify the `vc_position` of all former members
24+
* [#20750](https://github.com/netbox-community/netbox/issues/20750) - Fix cloning of permissions when only one action is enabled
25+
* [#20755](https://github.com/netbox-community/netbox/issues/20755) - Prevent duplicate results under certain conditions when filtering providers
26+
* [#20771](https://github.com/netbox-community/netbox/issues/20771) - Comments are required when creating a new journal entry
27+
* [#20774](https://github.com/netbox-community/netbox/issues/20774) - Bulk action button labels should be translated
28+
29+
---
30+
331
## v4.4.5 (2025-10-28)
432

533
### Enhancements

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ nav:
143143
- Getting Started: 'plugins/development/index.md'
144144
- Models: 'plugins/development/models.md'
145145
- Views: 'plugins/development/views.md'
146+
- UI Components: 'plugins/development/ui-components.md'
146147
- Navigation: 'plugins/development/navigation.md'
147148
- Templates: 'plugins/development/templates.md'
148149
- Tables: 'plugins/development/tables.md'

netbox/circuits/api/serializers_/circuits.py

Lines changed: 4 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from django.contrib.contenttypes.models import ContentType
2-
from drf_spectacular.utils import extend_schema_field
32
from rest_framework import serializers
43

54
from circuits.choices import CircuitPriorityChoices, CircuitStatusChoices, VirtualCircuitTerminationRoleChoices
@@ -11,12 +10,12 @@
1110
from dcim.api.serializers_.device_components import InterfaceSerializer
1211
from dcim.api.serializers_.cables import CabledObjectSerializer
1312
from netbox.api.fields import ChoiceField, ContentTypeField, RelatedObjectCountField
13+
from netbox.api.gfk_fields import GFKSerializerField
1414
from netbox.api.serializers import (
1515
NetBoxModelSerializer, OrganizationalModelSerializer, PrimaryModelSerializer, WritableNestedSerializer,
1616
)
1717
from netbox.choices import DistanceUnitChoices
1818
from tenancy.api.serializers_.tenants import TenantSerializer
19-
from utilities.api import get_serializer_for_model
2019
from .providers import ProviderAccountSerializer, ProviderNetworkSerializer, ProviderSerializer
2120

2221
__all__ = (
@@ -55,7 +54,7 @@ class CircuitCircuitTerminationSerializer(WritableNestedSerializer):
5554
default=None
5655
)
5756
termination_id = serializers.IntegerField(allow_null=True, required=False, default=None)
58-
termination = serializers.SerializerMethodField(read_only=True)
57+
termination = GFKSerializerField(read_only=True)
5958

6059
class Meta:
6160
model = CircuitTermination
@@ -64,14 +63,6 @@ class Meta:
6463
'upstream_speed', 'xconnect_id', 'description',
6564
]
6665

67-
@extend_schema_field(serializers.JSONField(allow_null=True))
68-
def get_termination(self, obj):
69-
if obj.termination_id is None:
70-
return None
71-
serializer = get_serializer_for_model(obj.termination)
72-
context = {'request': self.context['request']}
73-
return serializer(obj.termination, nested=True, context=context).data
74-
7566

7667
class CircuitGroupSerializer(OrganizationalModelSerializer):
7768
tenant = TenantSerializer(nested=True, required=False, allow_null=True)
@@ -134,7 +125,7 @@ class CircuitTerminationSerializer(NetBoxModelSerializer, CabledObjectSerializer
134125
default=None
135126
)
136127
termination_id = serializers.IntegerField(allow_null=True, required=False, default=None)
137-
termination = serializers.SerializerMethodField(read_only=True)
128+
termination = GFKSerializerField(read_only=True)
138129

139130
class Meta:
140131
model = CircuitTermination
@@ -146,20 +137,12 @@ class Meta:
146137
]
147138
brief_fields = ('id', 'url', 'display', 'circuit', 'term_side', 'description', 'cable', '_occupied')
148139

149-
@extend_schema_field(serializers.JSONField(allow_null=True))
150-
def get_termination(self, obj):
151-
if obj.termination_id is None:
152-
return None
153-
serializer = get_serializer_for_model(obj.termination)
154-
context = {'request': self.context['request']}
155-
return serializer(obj.termination, nested=True, context=context).data
156-
157140

158141
class CircuitGroupAssignmentSerializer(CircuitGroupAssignmentSerializer_):
159142
member_type = ContentTypeField(
160143
queryset=ContentType.objects.filter(CIRCUIT_GROUP_ASSIGNMENT_MEMBER_MODELS)
161144
)
162-
member = serializers.SerializerMethodField(read_only=True)
145+
member = GFKSerializerField(read_only=True)
163146

164147
class Meta:
165148
model = CircuitGroupAssignment
@@ -169,14 +152,6 @@ class Meta:
169152
]
170153
brief_fields = ('id', 'url', 'display', 'group', 'member_type', 'member_id', 'member', 'priority')
171154

172-
@extend_schema_field(serializers.JSONField(allow_null=True))
173-
def get_member(self, obj):
174-
if obj.member_id is None:
175-
return None
176-
serializer = get_serializer_for_model(obj.member)
177-
context = {'request': self.context['request']}
178-
return serializer(obj.member, nested=True, context=context).data
179-
180155

181156
class VirtualCircuitTypeSerializer(OrganizationalModelSerializer):
182157

0 commit comments

Comments
 (0)