Skip to content

Commit 4db3d48

Browse files
committed
Merge branch 'main' into 02496-max-page
2 parents 10e8e7b + b7cae04 commit 4db3d48

File tree

10 files changed

+77
-43
lines changed

10 files changed

+77
-43
lines changed

contrib/openapi.json

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -215569,24 +215569,26 @@
215569215569
"IntegerRange": {
215570215570
"type": "array",
215571215571
"items": {
215572-
"type": "array",
215573-
"items": {
215574-
"type": "integer"
215575-
},
215576-
"minItems": 2,
215577-
"maxItems": 2
215578-
}
215572+
"type": "integer"
215573+
},
215574+
"minItems": 2,
215575+
"maxItems": 2,
215576+
"example": [
215577+
10,
215578+
20
215579+
]
215579215580
},
215580215581
"IntegerRangeRequest": {
215581215582
"type": "array",
215582215583
"items": {
215583-
"type": "array",
215584-
"items": {
215585-
"type": "integer"
215586-
},
215587-
"minItems": 2,
215588-
"maxItems": 2
215589-
}
215584+
"type": "integer"
215585+
},
215586+
"minItems": 2,
215587+
"maxItems": 2,
215588+
"example": [
215589+
10,
215590+
20
215591+
]
215590215592
},
215591215593
"Interface": {
215592215594
"type": "object",
@@ -228986,7 +228988,6 @@
228986228988
},
228987228989
"key": {
228988228990
"type": "string",
228989-
"writeOnly": true,
228990228991
"maxLength": 40,
228991228992
"minLength": 40
228992228993
},
@@ -245221,6 +245222,11 @@
245221245222
"format": "date-time",
245222245223
"nullable": true
245223245224
},
245225+
"key": {
245226+
"type": "string",
245227+
"maxLength": 40,
245228+
"minLength": 40
245229+
},
245224245230
"write_enabled": {
245225245231
"type": "boolean",
245226245232
"description": "Permit create/update/delete operations using this key"
@@ -245367,7 +245373,6 @@
245367245373
},
245368245374
"key": {
245369245375
"type": "string",
245370-
"writeOnly": true,
245371245376
"maxLength": 40,
245372245377
"minLength": 40
245373245378
},

docs/plugins/development/filtersets.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Filters & Filter Sets
22

3-
Filter sets define the mechanisms available for filtering or searching through a set of objects in NetBox. For instance, sites can be filtered by their parent region or group, status, facility ID, and so on. The same filter set is used consistently for a model whether the request is made via the UI or REST API. (Note that the GraphQL API uses a separate filter class.) NetBox employs the [django-filters2](https://django-tables2.readthedocs.io/en/latest/) library to define filter sets.
3+
Filter sets define the mechanisms available for filtering or searching through a set of objects in NetBox. For instance, sites can be filtered by their parent region or group, status, facility ID, and so on. The same filter set is used consistently for a model whether the request is made via the UI or REST API. (Note that the GraphQL API uses a separate filter class.) NetBox employs the [django-filter](https://django-filter.readthedocs.io/en/stable/) library to define filter sets.
44

55
## FilterSet Classes
66

netbox/circuits/apps.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from django.apps import AppConfig
22

3+
from netbox import denormalized
4+
35

46
class CircuitsConfig(AppConfig):
57
name = "circuits"
@@ -8,6 +10,16 @@ class CircuitsConfig(AppConfig):
810
def ready(self):
911
from netbox.models.features import register_models
1012
from . import signals, search # noqa: F401
13+
from .models import CircuitTermination
1114

1215
# Register models
1316
register_models(*self.get_models())
17+
18+
denormalized.register(CircuitTermination, '_site', {
19+
'_region': 'region',
20+
'_site_group': 'group',
21+
})
22+
23+
denormalized.register(CircuitTermination, '_location', {
24+
'_site': 'site',
25+
})

netbox/core/api/schema.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -282,18 +282,18 @@ def map_serializer_field(self, auto_schema, direction):
282282

283283
class FixIntegerRangeSerializerSchema(OpenApiSerializerExtension):
284284
target_class = 'netbox.api.fields.IntegerRangeSerializer'
285+
match_subclasses = True
285286

286287
def map_serializer(self, auto_schema: 'AutoSchema', direction: Direction) -> _SchemaType:
288+
# One range = two integers; many=True will wrap this in an outer array
287289
return {
288290
'type': 'array',
289291
'items': {
290-
'type': 'array',
291-
'items': {
292-
'type': 'integer',
293-
},
294-
'minItems': 2,
295-
'maxItems': 2,
292+
'type': 'integer',
296293
},
294+
'minItems': 2,
295+
'maxItems': 2,
296+
'example': [10, 20],
297297
}
298298

299299

netbox/ipam/graphql/types.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def family(self) -> IPAddressFamilyType:
7474
filters=ASNFilter,
7575
pagination=True
7676
)
77-
class ASNType(NetBoxObjectType):
77+
class ASNType(NetBoxObjectType, ContactsMixin):
7878
asn: BigInt
7979
rir: Annotated["RIRType", strawberry.lazy('ipam.graphql.types')] | None
8080
tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None

netbox/netbox/api/fields.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ def to_internal_value(self, data):
169169
if type(data[0]) is not int or type(data[1]) is not int:
170170
raise ValidationError(_("Range boundaries must be defined as integers."))
171171

172-
return NumericRange(data[0], data[1], bounds='[]')
172+
return NumericRange(data[0], data[1] + 1, bounds='[)')
173173

174174
def to_representation(self, instance):
175175
return instance.lower, instance.upper - 1

netbox/templates/extras/htmx/script_result.html

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ <h2 class="card-header">{% trans "Log" %}</h2>
4444
<div class="htmx-container table-responsive"
4545
hx-get="{% url 'extras:script_result' job_pk=job.pk %}?embedded=True&log=True&log_threshold={{log_threshold}}"
4646
hx-target="this"
47-
hx-trigger="load" hx-select=".htmx-container" hx-swap="outerHTML"
48-
></div>
47+
hx-trigger="load" hx-select=".htmx-container" hx-swap="outerHTML">
48+
</div>
4949
</div>
5050
</div>
5151
{% endif %}
@@ -60,11 +60,12 @@ <h2 class="card-header d-flex justify-content-between">
6060
<a href="?export=output" class="btn btn-sm btn-primary" role="button">
6161
<i class="mdi mdi-download" aria-hidden="true"></i> {% trans "Download" %}
6262
</a>
63+
{% copy_content "job_data_output" %}
6364
</div>
6465
{% endif %}
6566
</h2>
6667
{% if job.data.output %}
67-
<pre class="card-body font-monospace">{{ job.data.output }}</pre>
68+
<pre class="card-body font-monospace" id="job_data_output">{{ job.data.output }}</pre>
6869
{% else %}
6970
<div class="card-body text-muted">{% trans "None" %}</div>
7071
{% endif %}

netbox/translations/en/LC_MESSAGES/django.po

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ msgid ""
88
msgstr ""
99
"Project-Id-Version: PACKAGE VERSION\n"
1010
"Report-Msgid-Bugs-To: \n"
11-
"POT-Creation-Date: 2025-10-02 05:01+0000\n"
11+
"POT-Creation-Date: 2025-10-07 05:02+0000\n"
1212
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
1313
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
1414
"Language-Team: LANGUAGE <[email protected]>\n"
@@ -12755,7 +12755,7 @@ msgstr ""
1275512755
#: netbox/templates/extras/configtemplate.html:77
1275612756
#: netbox/templates/extras/eventrule.html:66
1275712757
#: netbox/templates/extras/exporttemplate.html:60
12758-
#: netbox/templates/extras/htmx/script_result.html:69
12758+
#: netbox/templates/extras/htmx/script_result.html:70
1275912759
#: netbox/templates/extras/webhook.html:65
1276012760
#: netbox/templates/extras/webhook.html:75
1276112761
#: netbox/templates/inc/panel_table.html:13

netbox/utilities/data.py

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -137,24 +137,40 @@ def check_ranges_overlap(ranges):
137137

138138
def ranges_to_string(ranges):
139139
"""
140-
Generate a human-friendly string from a set of ranges. Intended for use with ArrayField. For example:
141-
[[1, 100)], [200, 300)] => "1-99,200-299"
140+
Converts a list of ranges into a string representation.
141+
142+
This function takes a list of range objects and produces a string
143+
representation of those ranges. Each range is represented as a
144+
hyphen-separated pair of lower and upper bounds, with inclusive or
145+
exclusive bounds adjusted accordingly. If the lower and upper bounds
146+
of a range are the same, only the single value is added to the string.
147+
Intended for use with ArrayField.
148+
149+
Example:
150+
[NumericRange(1, 5), NumericRange(8, 9), NumericRange(10, 12)] => "1-5,8,10-12"
142151
"""
143152
if not ranges:
144153
return ''
145154
output = []
146155
for r in ranges:
147156
lower = r.lower if r.lower_inc else r.lower + 1
148157
upper = r.upper if r.upper_inc else r.upper - 1
149-
output.append(f'{lower}-{upper}')
158+
output.append(f"{lower}-{upper}" if lower != upper else str(lower))
150159
return ','.join(output)
151160

152161

153162
def string_to_ranges(value):
154163
"""
155-
Given a string in the format "1-100, 200-300" return an list of NumericRanges. Intended for use with ArrayField.
156-
For example:
157-
"1-99,200-299" => [NumericRange(1, 100), NumericRange(200, 300)]
164+
Converts a string representation of numeric ranges into a list of NumericRange objects.
165+
166+
This function parses a string containing numeric values and ranges separated by commas (e.g.,
167+
"1-5,8,10-12") and converts it into a list of NumericRange objects.
168+
In the case of a single integer, it is treated as a range where the start and end
169+
are equal. The returned ranges are represented as half-open intervals [lower, upper).
170+
Intended for use with ArrayField.
171+
172+
Example:
173+
"1-5,8,10-12" => [NumericRange(1, 6), NumericRange(8, 9), NumericRange(10, 13)]
158174
"""
159175
if not value:
160176
return None
@@ -172,5 +188,5 @@ def string_to_ranges(value):
172188
upper = dash_range[1]
173189
else:
174190
return None
175-
values.append(NumericRange(int(lower), int(upper), bounds='[]'))
191+
values.append(NumericRange(int(lower), int(upper) + 1, bounds='[)'))
176192
return values

netbox/utilities/tests/test_data.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,18 +61,18 @@ def test_string_to_ranges(self):
6161
self.assertEqual(
6262
string_to_ranges('10-19, 30-39, 100-199'),
6363
[
64-
NumericRange(10, 19, bounds='[]'), # 10-19
65-
NumericRange(30, 39, bounds='[]'), # 30-39
66-
NumericRange(100, 199, bounds='[]'), # 100-199
64+
NumericRange(10, 20, bounds='[)'), # 10-20
65+
NumericRange(30, 40, bounds='[)'), # 30-40
66+
NumericRange(100, 200, bounds='[)'), # 100-200
6767
]
6868
)
6969

7070
self.assertEqual(
7171
string_to_ranges('1-2, 5, 10-12'),
7272
[
73-
NumericRange(1, 2, bounds='[]'), # 1-2
74-
NumericRange(5, 5, bounds='[]'), # 5-5
75-
NumericRange(10, 12, bounds='[]'), # 10-12
73+
NumericRange(1, 3, bounds='[)'), # 1-3
74+
NumericRange(5, 6, bounds='[)'), # 5-6
75+
NumericRange(10, 13, bounds='[)'), # 10-13
7676
]
7777
)
7878

0 commit comments

Comments
 (0)