mirror of
https://github.com/netbox-community/netbox.git
synced 2026-02-04 14:26:25 -06:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2a581da47 | ||
|
|
bec5ecf6a9 | ||
|
|
c98f55dbd2 | ||
|
|
03853c3120 | ||
|
|
dc6a54ec21 |
@@ -8,7 +8,7 @@ This is a mapping of models to [custom validators](../customization/custom-valid
|
||||
|
||||
```python
|
||||
CUSTOM_VALIDATORS = {
|
||||
"dcim.site": [
|
||||
"dcim.Site": [
|
||||
{
|
||||
"name": {
|
||||
"min_length": 5,
|
||||
@@ -17,12 +17,15 @@ CUSTOM_VALIDATORS = {
|
||||
},
|
||||
"my_plugin.validators.Validator1"
|
||||
],
|
||||
"dcim.device": [
|
||||
"dcim.Device": [
|
||||
"my_plugin.validators.Validator1"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
!!! info "Case-Insensitive Model Names"
|
||||
Model identifiers are case-insensitive. Both `dcim.site` and `dcim.Site` are valid and equivalent.
|
||||
|
||||
---
|
||||
|
||||
## FIELD_CHOICES
|
||||
@@ -53,6 +56,9 @@ FIELD_CHOICES = {
|
||||
}
|
||||
```
|
||||
|
||||
!!! info "Case-Insensitive Field Identifiers"
|
||||
Field identifiers are case-insensitive. Both `dcim.Site.status` and `dcim.site.status` are valid and equivalent.
|
||||
|
||||
The following model fields support configurable choices:
|
||||
|
||||
* `circuits.Circuit.status`
|
||||
@@ -98,7 +104,7 @@ This is a mapping of models to [custom validators](../customization/custom-valid
|
||||
|
||||
```python
|
||||
PROTECTION_RULES = {
|
||||
"dcim.site": [
|
||||
"dcim.Site": [
|
||||
{
|
||||
"status": {
|
||||
"eq": "decommissioning"
|
||||
@@ -108,3 +114,6 @@ PROTECTION_RULES = {
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
!!! info "Case-Insensitive Model Names"
|
||||
Model identifiers are case-insensitive. Both `dcim.site` and `dcim.Site` are valid and equivalent.
|
||||
|
||||
@@ -18,6 +18,7 @@ from extras.events import enqueue_event
|
||||
from extras.models import Tag
|
||||
from extras.utils import run_validators
|
||||
from netbox.config import get_config
|
||||
from utilities.data import get_config_value_ci
|
||||
from netbox.context import current_request, events_queue
|
||||
from netbox.models.features import ChangeLoggingMixin, get_model_features, model_is_public
|
||||
from utilities.exceptions import AbortRequest
|
||||
@@ -168,7 +169,7 @@ def handle_deleted_object(sender, instance, **kwargs):
|
||||
# to queueing any events for the object being deleted, in case a validation error is
|
||||
# raised, causing the deletion to fail.
|
||||
model_name = f'{sender._meta.app_label}.{sender._meta.model_name}'
|
||||
validators = get_config().PROTECTION_RULES.get(model_name, [])
|
||||
validators = get_config_value_ci(get_config().PROTECTION_RULES, model_name, default=[])
|
||||
try:
|
||||
run_validators(instance, validators)
|
||||
except ValidationError as e:
|
||||
|
||||
@@ -125,7 +125,7 @@ class DeviceManagementPanel(panels.ObjectAttributesPanel):
|
||||
class DeviceDimensionsPanel(panels.ObjectAttributesPanel):
|
||||
title = _('Dimensions')
|
||||
|
||||
height = attrs.TextAttr('device_type.u_height', format_string='{}U')
|
||||
height = attrs.TemplatedAttr('device_type.u_height', template_name='dcim/devicetype/attrs/height.html')
|
||||
total_weight = attrs.TemplatedAttr('total_weight', template_name='dcim/device/attrs/total_weight.html')
|
||||
|
||||
|
||||
@@ -135,7 +135,7 @@ class DeviceTypePanel(panels.ObjectAttributesPanel):
|
||||
part_number = attrs.TextAttr('part_number')
|
||||
default_platform = attrs.RelatedObjectAttr('default_platform', linkify=True)
|
||||
description = attrs.TextAttr('description')
|
||||
height = attrs.TextAttr('u_height', format_string='{}U', label=_('Height'))
|
||||
height = attrs.TemplatedAttr('u_height', template_name='dcim/devicetype/attrs/height.html')
|
||||
exclude_from_utilization = attrs.BooleanAttr('exclude_from_utilization')
|
||||
full_depth = attrs.BooleanAttr('is_full_depth')
|
||||
weight = attrs.NumericAttr('weight', unit_accessor='get_weight_unit_display')
|
||||
|
||||
@@ -75,10 +75,11 @@ def get_bookmarks_object_type_choices():
|
||||
def get_models_from_content_types(content_types):
|
||||
"""
|
||||
Return a list of models corresponding to the given content types, identified by natural key.
|
||||
Accepts both lowercase (e.g. "dcim.site") and PascalCase (e.g. "dcim.Site") model names.
|
||||
"""
|
||||
models = []
|
||||
for content_type_id in content_types:
|
||||
app_label, model_name = content_type_id.split('.')
|
||||
app_label, model_name = content_type_id.lower().split('.')
|
||||
try:
|
||||
content_type = ObjectType.objects.get_by_natural_key(app_label, model_name)
|
||||
if content_type.model_class():
|
||||
|
||||
@@ -9,6 +9,7 @@ from extras.models import EventRule, Notification, Subscription
|
||||
from netbox.config import get_config
|
||||
from netbox.models.features import has_feature
|
||||
from netbox.signals import post_clean
|
||||
from utilities.data import get_config_value_ci
|
||||
from utilities.exceptions import AbortRequest
|
||||
from .models import CustomField, TaggedItem
|
||||
from .utils import run_validators
|
||||
@@ -65,7 +66,7 @@ def run_save_validators(sender, instance, **kwargs):
|
||||
Run any custom validation rules for the model prior to calling save().
|
||||
"""
|
||||
model_name = f'{sender._meta.app_label}.{sender._meta.model_name}'
|
||||
validators = get_config().CUSTOM_VALIDATORS.get(model_name, [])
|
||||
validators = get_config_value_ci(get_config().CUSTOM_VALIDATORS, model_name, default=[])
|
||||
|
||||
run_validators(instance, validators)
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ class TextAttr(ObjectAttribute):
|
||||
def get_value(self, obj):
|
||||
value = resolve_attr_path(obj, self.accessor)
|
||||
# Apply format string (if any)
|
||||
if value and self.format_string:
|
||||
if value is not None and value != '' and self.format_string:
|
||||
return self.format_string.format(value)
|
||||
return value
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{{ value|floatformat }}U
|
||||
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-01-29 05:16+0000\n"
|
||||
"POT-Creation-Date: 2026-01-30 05:18+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"
|
||||
@@ -293,8 +293,8 @@ msgstr ""
|
||||
|
||||
#: netbox/circuits/filtersets.py:278 netbox/circuits/filtersets.py:382
|
||||
#: netbox/circuits/filtersets.py:547 netbox/core/filtersets.py:85
|
||||
#: netbox/core/filtersets.py:150 netbox/core/filtersets.py:176
|
||||
#: netbox/core/filtersets.py:216 netbox/dcim/filtersets.py:810
|
||||
#: netbox/core/filtersets.py:154 netbox/core/filtersets.py:180
|
||||
#: netbox/core/filtersets.py:220 netbox/dcim/filtersets.py:810
|
||||
#: netbox/dcim/filtersets.py:1568 netbox/dcim/filtersets.py:2692
|
||||
#: netbox/extras/filtersets.py:48 netbox/extras/filtersets.py:71
|
||||
#: netbox/extras/filtersets.py:101 netbox/extras/filtersets.py:142
|
||||
@@ -763,7 +763,7 @@ msgstr ""
|
||||
#: netbox/circuits/forms/filtersets.py:132
|
||||
#: netbox/circuits/forms/filtersets.py:322
|
||||
#: netbox/circuits/forms/filtersets.py:338 netbox/core/forms/filtersets.py:75
|
||||
#: netbox/core/forms/filtersets.py:143 netbox/dcim/forms/bulk_edit.py:818
|
||||
#: netbox/core/forms/filtersets.py:147 netbox/dcim/forms/bulk_edit.py:818
|
||||
#: netbox/dcim/forms/bulk_import.py:480 netbox/dcim/forms/filtersets.py:199
|
||||
#: netbox/dcim/forms/filtersets.py:232 netbox/dcim/forms/filtersets.py:1012
|
||||
#: netbox/dcim/forms/filtersets.py:1155 netbox/dcim/forms/filtersets.py:1285
|
||||
@@ -2189,7 +2189,7 @@ msgstr ""
|
||||
msgid "Data source (name)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/filtersets.py:186 netbox/dcim/filtersets.py:521
|
||||
#: netbox/core/filtersets.py:190 netbox/dcim/filtersets.py:521
|
||||
#: netbox/extras/filtersets.py:302 netbox/extras/filtersets.py:355
|
||||
#: netbox/extras/filtersets.py:401 netbox/extras/filtersets.py:424
|
||||
#: netbox/extras/filtersets.py:490 netbox/users/filtersets.py:31
|
||||
@@ -2197,7 +2197,7 @@ msgstr ""
|
||||
msgid "User (ID)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/filtersets.py:192
|
||||
#: netbox/core/filtersets.py:196
|
||||
msgid "User name"
|
||||
msgstr ""
|
||||
|
||||
@@ -2277,7 +2277,7 @@ msgstr ""
|
||||
msgid "Creation"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/forms/filtersets.py:82 netbox/core/forms/filtersets.py:168
|
||||
#: netbox/core/forms/filtersets.py:82 netbox/core/forms/filtersets.py:172
|
||||
#: netbox/extras/forms/filtersets.py:577 netbox/extras/tables/tables.py:271
|
||||
#: netbox/extras/tables/tables.py:338 netbox/extras/tables/tables.py:364
|
||||
#: netbox/extras/tables/tables.py:383 netbox/extras/tables/tables.py:415
|
||||
@@ -2288,39 +2288,44 @@ msgstr ""
|
||||
msgid "Object Type"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/forms/filtersets.py:92
|
||||
#: netbox/core/forms/filtersets.py:92 netbox/core/tables/jobs.py:46
|
||||
#: netbox/templates/core/job.html:63 netbox/templates/core/rq_task.html:61
|
||||
msgid "Queue"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/forms/filtersets.py:96
|
||||
msgid "Created after"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/forms/filtersets.py:97
|
||||
#: netbox/core/forms/filtersets.py:101
|
||||
msgid "Created before"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/forms/filtersets.py:102
|
||||
#: netbox/core/forms/filtersets.py:106
|
||||
msgid "Scheduled after"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/forms/filtersets.py:107
|
||||
#: netbox/core/forms/filtersets.py:111
|
||||
msgid "Scheduled before"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/forms/filtersets.py:112
|
||||
#: netbox/core/forms/filtersets.py:116
|
||||
msgid "Started after"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/forms/filtersets.py:117
|
||||
#: netbox/core/forms/filtersets.py:121
|
||||
msgid "Started before"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/forms/filtersets.py:122
|
||||
#: netbox/core/forms/filtersets.py:126
|
||||
msgid "Completed after"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/forms/filtersets.py:127
|
||||
#: netbox/core/forms/filtersets.py:131
|
||||
msgid "Completed before"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/forms/filtersets.py:134 netbox/core/forms/filtersets.py:163
|
||||
#: netbox/core/forms/filtersets.py:138 netbox/core/forms/filtersets.py:167
|
||||
#: netbox/dcim/forms/bulk_edit.py:455 netbox/dcim/forms/filtersets.py:509
|
||||
#: netbox/dcim/forms/model_forms.py:326 netbox/extras/forms/filtersets.py:572
|
||||
#: netbox/extras/forms/filtersets.py:592 netbox/extras/tables/tables.py:391
|
||||
@@ -2336,22 +2341,22 @@ msgstr ""
|
||||
msgid "User"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/forms/filtersets.py:142 netbox/core/tables/change_logging.py:15
|
||||
#: netbox/core/tables/jobs.py:69 netbox/extras/tables/tables.py:773
|
||||
#: netbox/core/forms/filtersets.py:146 netbox/core/tables/change_logging.py:15
|
||||
#: netbox/core/tables/jobs.py:72 netbox/extras/tables/tables.py:773
|
||||
#: netbox/extras/tables/tables.py:828
|
||||
#: netbox/templates/core/objectchange.html:32
|
||||
msgid "Time"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/forms/filtersets.py:147 netbox/extras/forms/filtersets.py:561
|
||||
#: netbox/core/forms/filtersets.py:151 netbox/extras/forms/filtersets.py:561
|
||||
msgid "After"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/forms/filtersets.py:152 netbox/extras/forms/filtersets.py:566
|
||||
#: netbox/core/forms/filtersets.py:156 netbox/extras/forms/filtersets.py:566
|
||||
msgid "Before"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/forms/filtersets.py:156 netbox/core/tables/change_logging.py:29
|
||||
#: netbox/core/forms/filtersets.py:160 netbox/core/tables/change_logging.py:29
|
||||
#: netbox/extras/forms/model_forms.py:484
|
||||
#: netbox/templates/core/objectchange.html:46
|
||||
#: netbox/templates/extras/eventrule.html:71
|
||||
@@ -2721,28 +2726,36 @@ msgid "job ID"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/models/jobs.py:116
|
||||
msgid "queue name"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/models/jobs.py:119
|
||||
msgid "Name of the queue in which this job was enqueued"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/models/jobs.py:122
|
||||
msgid "log entries"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/models/jobs.py:132
|
||||
#: netbox/core/models/jobs.py:138
|
||||
msgid "job"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/models/jobs.py:133
|
||||
#: netbox/core/models/jobs.py:139
|
||||
msgid "jobs"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/models/jobs.py:163
|
||||
#: netbox/core/models/jobs.py:169
|
||||
#, python-brace-format
|
||||
msgid "Jobs cannot be assigned to this object type ({type})."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/models/jobs.py:216
|
||||
#: netbox/core/models/jobs.py:226
|
||||
#, python-brace-format
|
||||
msgid "Invalid status for job termination. Choices are: {choices}"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/models/jobs.py:273
|
||||
#: netbox/core/models/jobs.py:283
|
||||
msgid ""
|
||||
"enqueue() cannot be called with values for both schedule_at and immediate."
|
||||
msgstr ""
|
||||
@@ -2788,7 +2801,7 @@ msgstr ""
|
||||
msgid "Request ID"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/tables/change_logging.py:45 netbox/core/tables/jobs.py:76
|
||||
#: netbox/core/tables/change_logging.py:45 netbox/core/tables/jobs.py:79
|
||||
#: netbox/extras/tables/tables.py:784 netbox/extras/tables/tables.py:841
|
||||
#: netbox/templates/core/objectchange.html:68
|
||||
msgid "Message"
|
||||
@@ -2831,16 +2844,16 @@ msgstr ""
|
||||
msgid "Interval"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/tables/jobs.py:46
|
||||
#: netbox/core/tables/jobs.py:49
|
||||
msgid "Log Entries"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/tables/jobs.py:73 netbox/extras/tables/tables.py:778
|
||||
#: netbox/core/tables/jobs.py:76 netbox/extras/tables/tables.py:778
|
||||
#: netbox/extras/tables/tables.py:832
|
||||
msgid "Level"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/tables/jobs.py:80
|
||||
#: netbox/core/tables/jobs.py:83
|
||||
msgid "No log entries"
|
||||
msgstr ""
|
||||
|
||||
@@ -8873,7 +8886,7 @@ msgstr ""
|
||||
|
||||
#: netbox/extras/forms/filtersets.py:176 netbox/extras/forms/filtersets.py:377
|
||||
#: netbox/extras/forms/filtersets.py:400 netbox/extras/forms/filtersets.py:496
|
||||
#: netbox/extras/forms/model_forms.py:690 netbox/templates/core/job.html:69
|
||||
#: netbox/extras/forms/model_forms.py:690 netbox/templates/core/job.html:73
|
||||
#: netbox/templates/extras/eventrule.html:84
|
||||
msgid "Data"
|
||||
msgstr ""
|
||||
@@ -13574,10 +13587,6 @@ msgstr ""
|
||||
msgid "Enqueue"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/templates/core/rq_task.html:61
|
||||
msgid "Queue"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/templates/core/rq_task.html:65
|
||||
msgid "Timeout"
|
||||
msgstr ""
|
||||
|
||||
@@ -3,6 +3,7 @@ import enum
|
||||
from django.conf import settings
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from utilities.data import get_config_value_ci
|
||||
from utilities.string import enum_key
|
||||
|
||||
__all__ = (
|
||||
@@ -24,13 +25,14 @@ class ChoiceSetMeta(type):
|
||||
).format(name=name)
|
||||
app = attrs['__module__'].split('.', 1)[0]
|
||||
replace_key = f'{app}.{key}'
|
||||
extend_key = f'{replace_key}+' if replace_key else None
|
||||
if replace_key and replace_key in settings.FIELD_CHOICES:
|
||||
# Replace the stock choices
|
||||
attrs['CHOICES'] = settings.FIELD_CHOICES[replace_key]
|
||||
elif extend_key and extend_key in settings.FIELD_CHOICES:
|
||||
# Extend the stock choices
|
||||
attrs['CHOICES'].extend(settings.FIELD_CHOICES[extend_key])
|
||||
replace_choices = get_config_value_ci(settings.FIELD_CHOICES, replace_key)
|
||||
if replace_choices is not None:
|
||||
attrs['CHOICES'] = replace_choices
|
||||
else:
|
||||
extend_key = f'{replace_key}+'
|
||||
extend_choices = get_config_value_ci(settings.FIELD_CHOICES, extend_key)
|
||||
if extend_choices is not None:
|
||||
attrs['CHOICES'].extend(extend_choices)
|
||||
|
||||
# Define choice tuples and color maps
|
||||
attrs['_choices'] = []
|
||||
|
||||
@@ -10,6 +10,7 @@ __all__ = (
|
||||
'deepmerge',
|
||||
'drange',
|
||||
'flatten_dict',
|
||||
'get_config_value_ci',
|
||||
'ranges_to_string',
|
||||
'ranges_to_string_list',
|
||||
'resolve_attr_path',
|
||||
@@ -22,6 +23,19 @@ __all__ = (
|
||||
# Dictionary utilities
|
||||
#
|
||||
|
||||
def get_config_value_ci(config_dict, key, default=None):
|
||||
"""
|
||||
Retrieve a value from a dictionary using case-insensitive key matching.
|
||||
"""
|
||||
if key in config_dict:
|
||||
return config_dict[key]
|
||||
key_lower = key.lower()
|
||||
for config_key, value in config_dict.items():
|
||||
if config_key.lower() == key_lower:
|
||||
return value
|
||||
return default
|
||||
|
||||
|
||||
def deepmerge(original, new):
|
||||
"""
|
||||
Deep merge two dictionaries (new into original) and return a new dict
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from django.test import TestCase
|
||||
from django.test import TestCase, override_settings
|
||||
|
||||
from utilities.choices import ChoiceSet
|
||||
|
||||
@@ -30,3 +30,29 @@ class ChoiceSetTestCase(TestCase):
|
||||
|
||||
def test_values(self):
|
||||
self.assertListEqual(ExampleChoices.values(), ['a', 'b', 'c', 1, 2, 3])
|
||||
|
||||
|
||||
class FieldChoicesCaseInsensitiveTestCase(TestCase):
|
||||
"""
|
||||
Integration tests for FIELD_CHOICES case-insensitive key lookup.
|
||||
"""
|
||||
|
||||
def test_replace_choices_with_different_casing(self):
|
||||
"""Test that replacement works when config key casing differs."""
|
||||
# Config uses lowercase, but code constructs PascalCase key
|
||||
with override_settings(FIELD_CHOICES={'utilities.teststatus': [('new', 'New')]}):
|
||||
class TestStatusChoices(ChoiceSet):
|
||||
key = 'TestStatus' # Code will look up 'utilities.TestStatus'
|
||||
CHOICES = [('old', 'Old')]
|
||||
|
||||
self.assertEqual(TestStatusChoices.CHOICES, [('new', 'New')])
|
||||
|
||||
def test_extend_choices_with_different_casing(self):
|
||||
"""Test that extension works with the + suffix under casing differences."""
|
||||
# Config uses lowercase with + suffix
|
||||
with override_settings(FIELD_CHOICES={'utilities.teststatus+': [('extra', 'Extra')]}):
|
||||
class TestStatusChoices(ChoiceSet):
|
||||
key = 'TestStatus' # Code will look up 'utilities.TestStatus+'
|
||||
CHOICES = [('base', 'Base')]
|
||||
|
||||
self.assertEqual(TestStatusChoices.CHOICES, [('base', 'Base'), ('extra', 'Extra')])
|
||||
|
||||
@@ -2,6 +2,7 @@ from django.db.backends.postgresql.psycopg_any import NumericRange
|
||||
from django.test import TestCase
|
||||
from utilities.data import (
|
||||
check_ranges_overlap,
|
||||
get_config_value_ci,
|
||||
ranges_to_string,
|
||||
ranges_to_string_list,
|
||||
string_to_ranges,
|
||||
@@ -96,3 +97,25 @@ class RangeFunctionsTestCase(TestCase):
|
||||
string_to_ranges('2-10, a-b'),
|
||||
None # Fails to convert
|
||||
)
|
||||
|
||||
|
||||
class GetConfigValueCITestCase(TestCase):
|
||||
|
||||
def test_exact_match(self):
|
||||
config = {'dcim.site': 'value1', 'dcim.Device': 'value2'}
|
||||
self.assertEqual(get_config_value_ci(config, 'dcim.site'), 'value1')
|
||||
self.assertEqual(get_config_value_ci(config, 'dcim.Device'), 'value2')
|
||||
|
||||
def test_case_insensitive_match(self):
|
||||
config = {'dcim.Site': 'value1', 'ipam.IPAddress': 'value2'}
|
||||
self.assertEqual(get_config_value_ci(config, 'dcim.site'), 'value1')
|
||||
self.assertEqual(get_config_value_ci(config, 'ipam.ipaddress'), 'value2')
|
||||
|
||||
def test_default_value(self):
|
||||
config = {'dcim.site': 'value1'}
|
||||
self.assertIsNone(get_config_value_ci(config, 'nonexistent'))
|
||||
self.assertEqual(get_config_value_ci(config, 'nonexistent', default=[]), [])
|
||||
|
||||
def test_empty_dict(self):
|
||||
self.assertIsNone(get_config_value_ci({}, 'any.key'))
|
||||
self.assertEqual(get_config_value_ci({}, 'any.key', default=[]), [])
|
||||
|
||||
Reference in New Issue
Block a user