| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483 |
- from django import forms
- from django.test import TestCase
- from dcim.models import Site
- from netbox.choices import ImportFormatChoices
- from utilities.forms.bulk_import import BulkImportForm
- from utilities.forms.fields.csv import CSVSelectWidget
- from utilities.forms.forms import BulkRenameForm
- from utilities.forms.utils import get_field_value, expand_alphanumeric_pattern, expand_ipaddress_pattern
- class ExpandIPAddress(TestCase):
- """
- Validate the operation of expand_ipaddress_pattern().
- """
- def test_ipv4_range(self):
- input = '1.2.3.[9-10]/32'
- output = sorted([
- '1.2.3.9/32',
- '1.2.3.10/32',
- ])
- self.assertEqual(sorted(expand_ipaddress_pattern(input, 4)), output)
- def test_ipv4_set(self):
- input = '1.2.3.[4,44]/32'
- output = sorted([
- '1.2.3.4/32',
- '1.2.3.44/32',
- ])
- self.assertEqual(sorted(expand_ipaddress_pattern(input, 4)), output)
- def test_ipv4_multiple_ranges(self):
- input = '1.[9-10].3.[9-11]/32'
- output = sorted([
- '1.9.3.9/32',
- '1.9.3.10/32',
- '1.9.3.11/32',
- '1.10.3.9/32',
- '1.10.3.10/32',
- '1.10.3.11/32',
- ])
- self.assertEqual(sorted(expand_ipaddress_pattern(input, 4)), output)
- def test_ipv4_multiple_sets(self):
- input = '1.[2,22].3.[4,44]/32'
- output = sorted([
- '1.2.3.4/32',
- '1.2.3.44/32',
- '1.22.3.4/32',
- '1.22.3.44/32',
- ])
- self.assertEqual(sorted(expand_ipaddress_pattern(input, 4)), output)
- def test_ipv4_set_and_range(self):
- input = '1.[2,22].3.[9-11]/32'
- output = sorted([
- '1.2.3.9/32',
- '1.2.3.10/32',
- '1.2.3.11/32',
- '1.22.3.9/32',
- '1.22.3.10/32',
- '1.22.3.11/32',
- ])
- self.assertEqual(sorted(expand_ipaddress_pattern(input, 4)), output)
- def test_ipv6_range(self):
- input = 'fec::abcd:[9-b]/64'
- output = sorted([
- 'fec::abcd:9/64',
- 'fec::abcd:a/64',
- 'fec::abcd:b/64',
- ])
- self.assertEqual(sorted(expand_ipaddress_pattern(input, 6)), output)
- def test_ipv6_range_multichar_field(self):
- input = 'fec::abcd:[f-11]/64'
- output = sorted([
- 'fec::abcd:f/64',
- 'fec::abcd:10/64',
- 'fec::abcd:11/64',
- ])
- self.assertEqual(sorted(expand_ipaddress_pattern(input, 6)), output)
- def test_ipv6_set(self):
- input = 'fec::abcd:[9,ab]/64'
- output = sorted([
- 'fec::abcd:9/64',
- 'fec::abcd:ab/64',
- ])
- self.assertEqual(sorted(expand_ipaddress_pattern(input, 6)), output)
- def test_ipv6_multiple_ranges(self):
- input = 'fec::[1-2]bcd:[9-b]/64'
- output = sorted([
- 'fec::1bcd:9/64',
- 'fec::1bcd:a/64',
- 'fec::1bcd:b/64',
- 'fec::2bcd:9/64',
- 'fec::2bcd:a/64',
- 'fec::2bcd:b/64',
- ])
- self.assertEqual(sorted(expand_ipaddress_pattern(input, 6)), output)
- def test_ipv6_multiple_sets(self):
- input = 'fec::[a,f]bcd:[9,ab]/64'
- output = sorted([
- 'fec::abcd:9/64',
- 'fec::abcd:ab/64',
- 'fec::fbcd:9/64',
- 'fec::fbcd:ab/64',
- ])
- self.assertEqual(sorted(expand_ipaddress_pattern(input, 6)), output)
- def test_ipv6_set_and_range(self):
- input = 'fec::[dead,beaf]:[9-b]/64'
- output = sorted([
- 'fec::dead:9/64',
- 'fec::dead:a/64',
- 'fec::dead:b/64',
- 'fec::beaf:9/64',
- 'fec::beaf:a/64',
- 'fec::beaf:b/64',
- ])
- self.assertEqual(sorted(expand_ipaddress_pattern(input, 6)), output)
- def test_invalid_address_family(self):
- with self.assertRaisesRegex(Exception, 'Invalid IP address family: 5'):
- sorted(expand_ipaddress_pattern(None, 5))
- def test_invalid_non_pattern(self):
- with self.assertRaises(ValueError):
- sorted(expand_ipaddress_pattern('1.2.3.4/32', 4))
- def test_invalid_range(self):
- with self.assertRaises(ValueError):
- sorted(expand_ipaddress_pattern('1.2.3.[4-]/32', 4))
- with self.assertRaises(ValueError):
- sorted(expand_ipaddress_pattern('1.2.3.[-4]/32', 4))
- with self.assertRaises(ValueError):
- sorted(expand_ipaddress_pattern('1.2.3.[4--5]/32', 4))
- def test_invalid_range_bounds(self):
- self.assertEqual(sorted(expand_ipaddress_pattern('1.2.3.[4-3]/32', 6)), [])
- def test_invalid_set(self):
- with self.assertRaises(ValueError):
- sorted(expand_ipaddress_pattern('1.2.3.[4]/32', 4))
- with self.assertRaises(ValueError):
- sorted(expand_ipaddress_pattern('1.2.3.[4,]/32', 4))
- with self.assertRaises(ValueError):
- sorted(expand_ipaddress_pattern('1.2.3.[,4]/32', 4))
- with self.assertRaises(ValueError):
- sorted(expand_ipaddress_pattern('1.2.3.[4,,5]/32', 4))
- class ExpandAlphanumeric(TestCase):
- """
- Validate the operation of expand_alphanumeric_pattern().
- """
- def test_range_numberic(self):
- input = 'r[9-11]a'
- output = sorted([
- 'r9a',
- 'r10a',
- 'r11a',
- ])
- self.assertEqual(sorted(expand_alphanumeric_pattern(input)), output)
- def test_range_alpha(self):
- input = '[r-t]1a'
- output = sorted([
- 'r1a',
- 's1a',
- 't1a',
- ])
- self.assertEqual(sorted(expand_alphanumeric_pattern(input)), output)
- def test_set_numeric(self):
- input = 'r[1,2]a'
- output = sorted([
- 'r1a',
- 'r2a',
- ])
- self.assertEqual(sorted(expand_alphanumeric_pattern(input)), output)
- def test_set_alpha(self):
- input = '[r,t]1a'
- output = sorted([
- 'r1a',
- 't1a',
- ])
- self.assertEqual(sorted(expand_alphanumeric_pattern(input)), output)
- def test_set_multichar(self):
- input = '[ra,tb]1a'
- output = sorted([
- 'ra1a',
- 'tb1a',
- ])
- self.assertEqual(sorted(expand_alphanumeric_pattern(input)), output)
- def test_multiple_ranges(self):
- input = '[r-t]1[a-b]'
- output = sorted([
- 'r1a',
- 'r1b',
- 's1a',
- 's1b',
- 't1a',
- 't1b',
- ])
- self.assertEqual(sorted(expand_alphanumeric_pattern(input)), output)
- def test_multiple_sets(self):
- input = '[ra,tb]1[ax,by]'
- output = sorted([
- 'ra1ax',
- 'ra1by',
- 'tb1ax',
- 'tb1by',
- ])
- self.assertEqual(sorted(expand_alphanumeric_pattern(input)), output)
- def test_set_and_range(self):
- input = '[ra,tb]1[a-c]'
- output = sorted([
- 'ra1a',
- 'ra1b',
- 'ra1c',
- 'tb1a',
- 'tb1b',
- 'tb1c',
- ])
- self.assertEqual(sorted(expand_alphanumeric_pattern(input)), output)
- def test_invalid_non_pattern(self):
- with self.assertRaises(ValueError):
- sorted(expand_alphanumeric_pattern('r9a'))
- def test_invalid_range(self):
- with self.assertRaises(ValueError):
- sorted(expand_alphanumeric_pattern('r[8-]a'))
- with self.assertRaises(ValueError):
- sorted(expand_alphanumeric_pattern('r[-8]a'))
- with self.assertRaises(ValueError):
- sorted(expand_alphanumeric_pattern('r[8--9]a'))
- def test_invalid_range_alphanumeric(self):
- self.assertEqual(sorted(expand_alphanumeric_pattern('r[9-a]a')), [])
- self.assertEqual(sorted(expand_alphanumeric_pattern('r[a-9]a')), [])
- def test_invalid_range_bounds(self):
- with self.assertRaises(forms.ValidationError):
- sorted(expand_alphanumeric_pattern('r[9-8]a'))
- sorted(expand_alphanumeric_pattern('r[b-a]a'))
- def test_invalid_range_len(self):
- with self.assertRaises(forms.ValidationError):
- sorted(expand_alphanumeric_pattern('r[a-bb]a'))
- def test_invalid_set(self):
- with self.assertRaises(ValueError):
- sorted(expand_alphanumeric_pattern('r[a]a'))
- with self.assertRaises(ValueError):
- sorted(expand_alphanumeric_pattern('r[a,]a'))
- with self.assertRaises(ValueError):
- sorted(expand_alphanumeric_pattern('r[,a]a'))
- with self.assertRaises(ValueError):
- sorted(expand_alphanumeric_pattern('r[a,,b]a'))
- class ImportFormTest(TestCase):
- def test_format_detection(self):
- form = BulkImportForm()
- data = (
- "a,b,c\n"
- "1,2,3\n"
- "4,5,6\n"
- )
- self.assertEqual(form._detect_format(data), ImportFormatChoices.CSV)
- data = '{"a": 1, "b": 2, "c": 3"}'
- self.assertEqual(form._detect_format(data), ImportFormatChoices.JSON)
- data = '[{"a": 1, "b": 2, "c": 3"}, {"a": 4, "b": 5, "c": 6"}]'
- self.assertEqual(form._detect_format(data), ImportFormatChoices.JSON)
- data = (
- "- a: 1\n"
- " b: 2\n"
- " c: 3\n"
- "- a: 4\n"
- " b: 5\n"
- " c: 6\n"
- )
- self.assertEqual(form._detect_format(data), ImportFormatChoices.YAML)
- data = (
- "---\n"
- "a: 1\n"
- "b: 2\n"
- "c: 3\n"
- "---\n"
- "a: 4\n"
- "b: 5\n"
- "c: 6\n"
- )
- self.assertEqual(form._detect_format(data), ImportFormatChoices.YAML)
- # Invalid data
- with self.assertRaises(forms.ValidationError):
- form._detect_format('')
- with self.assertRaises(forms.ValidationError):
- form._detect_format('?')
- def test_csv_delimiters(self):
- form = BulkImportForm()
- data = (
- "a,b,c\n"
- "1,2,3\n"
- "4,5,6\n"
- )
- self.assertEqual(form._clean_csv(data, delimiter=','), [
- {'a': '1', 'b': '2', 'c': '3'},
- {'a': '4', 'b': '5', 'c': '6'},
- ])
- data = (
- "a;b;c\n"
- "1;2;3\n"
- "4;5;6\n"
- )
- self.assertEqual(form._clean_csv(data, delimiter=';'), [
- {'a': '1', 'b': '2', 'c': '3'},
- {'a': '4', 'b': '5', 'c': '6'},
- ])
- data = (
- "a\tb\tc\n"
- "1\t2\t3\n"
- "4\t5\t6\n"
- )
- self.assertEqual(form._clean_csv(data, delimiter='\t'), [
- {'a': '1', 'b': '2', 'c': '3'},
- {'a': '4', 'b': '5', 'c': '6'},
- ])
- class BulkRenameFormTest(TestCase):
- def test_no_strip_whitespace(self):
- # Tests to make sure Bulk Rename Form isn't stripping whitespaces
- # See: https://github.com/netbox-community/netbox/issues/13791
- form = BulkRenameForm(data={
- "find": " hello ",
- "replace": " world "
- })
- self.assertTrue(form.is_valid())
- self.assertEqual(form.cleaned_data["find"], " hello ")
- self.assertEqual(form.cleaned_data["replace"], " world ")
- class GetFieldValueTest(TestCase):
- @classmethod
- def setUpTestData(cls):
- class TestForm(forms.Form):
- site = forms.ModelChoiceField(
- queryset=Site.objects.all(),
- required=False
- )
- cls.form_class = TestForm
- cls.sites = (
- Site(name='Test Site 1', slug='test-site-1'),
- Site(name='Test Site 2', slug='test-site-2'),
- )
- Site.objects.bulk_create(cls.sites)
- def test_unbound_without_initial(self):
- form = self.form_class()
- self.assertEqual(
- get_field_value(form, 'site'),
- None
- )
- def test_unbound_with_initial(self):
- form = self.form_class(initial={'site': self.sites[0].pk})
- self.assertEqual(
- get_field_value(form, 'site'),
- self.sites[0].pk
- )
- def test_bound_value_without_initial(self):
- form = self.form_class({'site': self.sites[0].pk})
- self.assertEqual(
- get_field_value(form, 'site'),
- self.sites[0].pk
- )
- def test_bound_value_with_initial(self):
- form = self.form_class({'site': self.sites[0].pk}, initial={'site': self.sites[1].pk})
- self.assertEqual(
- get_field_value(form, 'site'),
- self.sites[0].pk
- )
- def test_bound_null_without_initial(self):
- form = self.form_class({'site': None})
- self.assertEqual(
- get_field_value(form, 'site'),
- None
- )
- def test_bound_null_with_initial(self):
- form = self.form_class({'site': None}, initial={'site': self.sites[1].pk})
- self.assertEqual(
- get_field_value(form, 'site'),
- None
- )
- class CSVSelectWidgetTest(TestCase):
- """
- Validate that CSVSelectWidget treats blank values as omitted.
- This allows model defaults to be applied when CSV fields are present but empty.
- Related to issue #20645.
- """
- def test_blank_value_treated_as_omitted(self):
- """Test that blank string values are treated as omitted"""
- widget = CSVSelectWidget()
- data = {'test_field': ''}
- self.assertTrue(widget.value_omitted_from_data(data, {}, 'test_field'))
- def test_none_value_treated_as_omitted(self):
- """Test that None values are treated as omitted"""
- widget = CSVSelectWidget()
- data = {'test_field': None}
- self.assertTrue(widget.value_omitted_from_data(data, {}, 'test_field'))
- def test_missing_field_treated_as_omitted(self):
- """Test that missing fields are treated as omitted"""
- widget = CSVSelectWidget()
- data = {}
- self.assertTrue(widget.value_omitted_from_data(data, {}, 'test_field'))
- def test_valid_value_not_omitted(self):
- """Test that valid values are not treated as omitted"""
- widget = CSVSelectWidget()
- data = {'test_field': 'valid_value'}
- self.assertFalse(widget.value_omitted_from_data(data, {}, 'test_field'))
|