Compare commits

...

6 Commits

Author SHA1 Message Date
github-actions
5a24f99c9d Update source translation strings
Some checks failed
CI / build (20.x, 3.10) (push) Has been cancelled
CI / build (20.x, 3.11) (push) Has been cancelled
CI / build (20.x, 3.12) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Lock threads / lock (push) Has been cancelled
Close stale issues/PRs / stale (push) Has been cancelled
Close incomplete issues / stale (push) Has been cancelled
Update translation strings / makemessages (push) Has been cancelled
2025-12-18 05:03:18 +00:00
Jeremy Stretch
9318c91405 Closes #20720: Add support for Latvian translations (#21003) 2025-12-17 15:20:04 -06:00
Martin Hauser
5c6aaf2388 Closes #20900: Allow multiple choices in CustomField select filter fields (#20992) 2025-12-17 14:32:46 -06:00
Jason Novinger
265f375595 Fixes #20876: Allow editing IPAddress in IPRange marked populated 2025-12-17 13:03:45 -05:00
bctiemann
2699149016 Merge pull request #20963 from pheus/20491-normalize-arrayfield-values-to-inclusive-pairs-for-api-tests
Fixes #20491: Normalize numeric range array fields for API test comparisons
2025-12-16 15:40:44 -05:00
Martin Hauser
60fce84c96 feat(ipam): Normalize numeric ranges in API output
Adds logic to handle numeric range fields in API responses by
converting them into inclusive `[low, high]` pairs for consistent
behavior. Updates test cases with `vid_ranges` fields to reflect the
changes.

Closes #20491
2025-12-10 21:11:23 +01:00
9 changed files with 16461 additions and 46 deletions

View File

@@ -5,7 +5,7 @@
<a href="https://github.com/netbox-community/netbox/blob/main/LICENSE.txt"><img src="https://img.shields.io/badge/license-Apache_2.0-blue.svg" alt="License" /></a>
<a href="https://github.com/netbox-community/netbox/graphs/contributors"><img src="https://img.shields.io/github/contributors/netbox-community/netbox?color=blue" alt="Contributors" /></a>
<a href="https://github.com/netbox-community/netbox/stargazers"><img src="https://img.shields.io/github/stars/netbox-community/netbox?style=flat" alt="GitHub stars" /></a>
<a href="https://explore.transifex.com/netbox-community/netbox/"><img src="https://img.shields.io/badge/languages-15-blue" alt="Languages supported" /></a>
<a href="https://explore.transifex.com/netbox-community/netbox/"><img src="https://img.shields.io/badge/languages-16-blue" alt="Languages supported" /></a>
<a href="https://github.com/netbox-community/netbox/actions/workflows/ci.yml"><img src="https://github.com/netbox-community/netbox/actions/workflows/ci.yml/badge.svg" alt="CI status" /></a>
<p>
<strong><a href="https://netboxlabs.com/community/">NetBox Community</a></strong> |

View File

@@ -449,7 +449,14 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
return model.objects.filter(pk__in=value)
return value
def to_form_field(self, set_initial=True, enforce_required=True, enforce_visibility=True, for_csv_import=False):
def to_form_field(
self,
set_initial=True,
enforce_required=True,
enforce_visibility=True,
for_csv_import=False,
for_filterset_form=False,
):
"""
Return a form field suitable for setting a CustomField's value for an object.
@@ -457,6 +464,7 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
enforce_required: Honor the value of CustomField.required. Set to False for filtering/bulk editing.
enforce_visibility: Honor the value of CustomField.ui_visible. Set to False for filtering.
for_csv_import: Return a form field suitable for bulk import of objects in CSV format.
for_filterset_form: Return a form field suitable for use in a FilterSet form.
"""
initial = self.default if set_initial else None
required = self.required if enforce_required else False
@@ -519,7 +527,7 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
field_class = CSVMultipleChoiceField
field = field_class(choices=choices, required=required, initial=initial)
else:
if self.type == CustomFieldTypeChoices.TYPE_SELECT:
if self.type == CustomFieldTypeChoices.TYPE_SELECT and not for_filterset_form:
field_class = DynamicChoiceField
widget_class = APISelect
else:

View File

@@ -910,13 +910,13 @@ class IPAddress(ContactsMixin, PrimaryModel):
})
# Disallow the creation of IPAddresses within an IPRange with mark_populated=True
parent_range = IPRange.objects.filter(
parent_range_qs = IPRange.objects.filter(
start_address__lte=self.address,
end_address__gte=self.address,
vrf=self.vrf,
mark_populated=True
).first()
if parent_range:
)
if not self.pk and (parent_range := parent_range_qs.first()):
raise ValidationError({
'address': _(
"Cannot create IP address {ip} inside range {range}."

View File

@@ -1071,14 +1071,17 @@ class VLANGroupTest(APIViewTestCases.APIViewTestCase):
{
'name': 'VLAN Group 4',
'slug': 'vlan-group-4',
'vid_ranges': [[1, 4094]]
},
{
'name': 'VLAN Group 5',
'slug': 'vlan-group-5',
'vid_ranges': [[1, 4094]]
},
{
'name': 'VLAN Group 6',
'slug': 'vlan-group-6',
'vid_ranges': [[1, 4094]]
},
]
bulk_update_data = {

View File

@@ -205,4 +205,6 @@ class NetBoxModelFilterSetForm(CustomFieldsMixin, SavedFiltersMixin, forms.Form)
)
def _get_form_field(self, customfield):
return customfield.to_form_field(set_initial=False, enforce_required=False, enforce_visibility=False)
return customfield.to_form_field(
set_initial=False, enforce_required=False, enforce_visibility=False, for_filterset_form=True
)

View File

@@ -827,6 +827,7 @@ LANGUAGES = (
('fr', _('French')),
('it', _('Italian')),
('ja', _('Japanese')),
('lv', _('Latvian')),
('nl', _('Dutch')),
('pl', _('Polish')),
('pt', _('Portuguese')),

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-13 05:01+0000\n"
"POT-Creation-Date: 2025-12-18 05:03+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -9267,113 +9267,113 @@ msgstr ""
msgid "Filter must be defined as a dictionary mapping attributes to values."
msgstr ""
#: netbox/extras/models/customfields.py:488
#: netbox/extras/models/customfields.py:496
msgid "True"
msgstr ""
#: netbox/extras/models/customfields.py:489
#: netbox/extras/models/customfields.py:497
msgid "False"
msgstr ""
#: netbox/extras/models/customfields.py:542
#: netbox/extras/models/customfields.py:590
#: netbox/extras/models/customfields.py:550
#: netbox/extras/models/customfields.py:598
#, python-brace-format
msgid "Values must match this regex: <code>{regex}</code>"
msgstr ""
#: netbox/extras/models/customfields.py:692
#: netbox/extras/models/customfields.py:699
#: netbox/extras/models/customfields.py:700
#: netbox/extras/models/customfields.py:707
msgid "Value must be a string."
msgstr ""
#: netbox/extras/models/customfields.py:694
#: netbox/extras/models/customfields.py:701
#: netbox/extras/models/customfields.py:702
#: netbox/extras/models/customfields.py:709
#, python-brace-format
msgid "Value must match regex '{regex}'"
msgstr ""
#: netbox/extras/models/customfields.py:706
#: netbox/extras/models/customfields.py:714
msgid "Value must be an integer."
msgstr ""
#: netbox/extras/models/customfields.py:709
#: netbox/extras/models/customfields.py:724
#: netbox/extras/models/customfields.py:717
#: netbox/extras/models/customfields.py:732
#, python-brace-format
msgid "Value must be at least {minimum}"
msgstr ""
#: netbox/extras/models/customfields.py:713
#: netbox/extras/models/customfields.py:728
#: netbox/extras/models/customfields.py:721
#: netbox/extras/models/customfields.py:736
#, python-brace-format
msgid "Value must not exceed {maximum}"
msgstr ""
#: netbox/extras/models/customfields.py:721
#: netbox/extras/models/customfields.py:729
msgid "Value must be a decimal."
msgstr ""
#: netbox/extras/models/customfields.py:733
#: netbox/extras/models/customfields.py:741
msgid "Value must be true or false."
msgstr ""
#: netbox/extras/models/customfields.py:741
#: netbox/extras/models/customfields.py:749
msgid "Date values must be in ISO 8601 format (YYYY-MM-DD)."
msgstr ""
#: netbox/extras/models/customfields.py:750
#: netbox/extras/models/customfields.py:758
msgid "Date and time values must be in ISO 8601 format (YYYY-MM-DD HH:MM:SS)."
msgstr ""
#: netbox/extras/models/customfields.py:757
#: netbox/extras/models/customfields.py:765
#, python-brace-format
msgid "Invalid choice ({value}) for choice set {choiceset}."
msgstr ""
#: netbox/extras/models/customfields.py:767
#: netbox/extras/models/customfields.py:775
#, python-brace-format
msgid "Invalid choice(s) ({value}) for choice set {choiceset}."
msgstr ""
#: netbox/extras/models/customfields.py:776
#: netbox/extras/models/customfields.py:784
#, python-brace-format
msgid "Value must be an object ID, not {type}"
msgstr ""
#: netbox/extras/models/customfields.py:782
#: netbox/extras/models/customfields.py:790
#, python-brace-format
msgid "Value must be a list of object IDs, not {type}"
msgstr ""
#: netbox/extras/models/customfields.py:786
#: netbox/extras/models/customfields.py:794
#, python-brace-format
msgid "Found invalid object ID: {id}"
msgstr ""
#: netbox/extras/models/customfields.py:789
#: netbox/extras/models/customfields.py:797
msgid "Required field cannot be empty."
msgstr ""
#: netbox/extras/models/customfields.py:809
#: netbox/extras/models/customfields.py:817
msgid "Base set of predefined choices (optional)"
msgstr ""
#: netbox/extras/models/customfields.py:821
#: netbox/extras/models/customfields.py:829
msgid "Choices are automatically ordered alphabetically"
msgstr ""
#: netbox/extras/models/customfields.py:828
#: netbox/extras/models/customfields.py:836
msgid "custom field choice set"
msgstr ""
#: netbox/extras/models/customfields.py:829
#: netbox/extras/models/customfields.py:837
msgid "custom field choice sets"
msgstr ""
#: netbox/extras/models/customfields.py:871
#: netbox/extras/models/customfields.py:879
msgid "Must define base or extra choices."
msgstr ""
#: netbox/extras/models/customfields.py:895
#: netbox/extras/models/customfields.py:903
#, python-brace-format
msgid ""
"Cannot remove choice {choice} as there are {model} objects which reference "
@@ -12523,30 +12523,34 @@ msgid "Japanese"
msgstr ""
#: netbox/netbox/settings.py:830
msgid "Dutch"
msgid "Latvian"
msgstr ""
#: netbox/netbox/settings.py:831
msgid "Polish"
msgid "Dutch"
msgstr ""
#: netbox/netbox/settings.py:832
msgid "Portuguese"
msgid "Polish"
msgstr ""
#: netbox/netbox/settings.py:833
msgid "Russian"
msgid "Portuguese"
msgstr ""
#: netbox/netbox/settings.py:834
msgid "Turkish"
msgid "Russian"
msgstr ""
#: netbox/netbox/settings.py:835
msgid "Ukrainian"
msgid "Turkish"
msgstr ""
#: netbox/netbox/settings.py:836
msgid "Ukrainian"
msgstr ""
#: netbox/netbox/settings.py:837
msgid "Chinese"
msgstr ""

File diff suppressed because it is too large Load Diff

View File

@@ -141,8 +141,8 @@ class ModelTestCase(TestCase):
elif value and type(field) is GenericForeignKey:
model_dict[key] = value.pk
# Handle API output
elif api:
# Replace ContentType numeric IDs with <app_label>.<model>
if type(getattr(instance, key)) in (ContentType, ObjectType):
object_type = ObjectType.objects.get(pk=value)
@@ -152,9 +152,13 @@ class ModelTestCase(TestCase):
elif type(value) is IPNetwork:
model_dict[key] = str(value)
else:
field = instance._meta.get_field(key)
# Normalize arrays of numeric ranges (e.g. VLAN IDs or port ranges).
# DB uses canonical half-open [lo, hi) via NumericRange; API uses inclusive [lo, hi].
# Convert to inclusive pairs for stable API comparisons.
elif type(field) is ArrayField and issubclass(type(field.base_field), RangeField):
model_dict[key] = [[r.lower, r.upper - 1] for r in value]
else:
# Convert ArrayFields to CSV strings
if type(field) is ArrayField:
if getattr(field.base_field, 'choices', None):