test_array.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716
  1. import decimal
  2. import json
  3. import unittest
  4. import uuid
  5. from django import forms
  6. from django.core import exceptions, serializers, validators
  7. from django.core.management import call_command
  8. from django.db import IntegrityError, connection, models
  9. from django.test import TransactionTestCase, override_settings
  10. from django.test.utils import isolate_apps
  11. from django.utils import timezone
  12. from . import PostgreSQLTestCase
  13. from .models import (
  14. ArrayFieldSubclass, CharArrayModel, DateTimeArrayModel, IntegerArrayModel,
  15. NestedIntegerArrayModel, NullableIntegerArrayModel, OtherTypesArrayModel,
  16. PostgreSQLModel, Tag,
  17. )
  18. try:
  19. from django.contrib.postgres.fields import ArrayField
  20. from django.contrib.postgres.forms import SimpleArrayField, SplitArrayField
  21. except ImportError:
  22. pass
  23. class TestSaveLoad(PostgreSQLTestCase):
  24. def test_integer(self):
  25. instance = IntegerArrayModel(field=[1, 2, 3])
  26. instance.save()
  27. loaded = IntegerArrayModel.objects.get()
  28. self.assertEqual(instance.field, loaded.field)
  29. def test_char(self):
  30. instance = CharArrayModel(field=['hello', 'goodbye'])
  31. instance.save()
  32. loaded = CharArrayModel.objects.get()
  33. self.assertEqual(instance.field, loaded.field)
  34. def test_dates(self):
  35. instance = DateTimeArrayModel(
  36. datetimes=[timezone.now()],
  37. dates=[timezone.now().date()],
  38. times=[timezone.now().time()],
  39. )
  40. instance.save()
  41. loaded = DateTimeArrayModel.objects.get()
  42. self.assertEqual(instance.datetimes, loaded.datetimes)
  43. self.assertEqual(instance.dates, loaded.dates)
  44. self.assertEqual(instance.times, loaded.times)
  45. def test_tuples(self):
  46. instance = IntegerArrayModel(field=(1,))
  47. instance.save()
  48. loaded = IntegerArrayModel.objects.get()
  49. self.assertSequenceEqual(instance.field, loaded.field)
  50. def test_integers_passed_as_strings(self):
  51. # This checks that get_prep_value is deferred properly
  52. instance = IntegerArrayModel(field=['1'])
  53. instance.save()
  54. loaded = IntegerArrayModel.objects.get()
  55. self.assertEqual(loaded.field, [1])
  56. def test_default_null(self):
  57. instance = NullableIntegerArrayModel()
  58. instance.save()
  59. loaded = NullableIntegerArrayModel.objects.get(pk=instance.pk)
  60. self.assertEqual(loaded.field, None)
  61. self.assertEqual(instance.field, loaded.field)
  62. def test_null_handling(self):
  63. instance = NullableIntegerArrayModel(field=None)
  64. instance.save()
  65. loaded = NullableIntegerArrayModel.objects.get()
  66. self.assertEqual(instance.field, loaded.field)
  67. instance = IntegerArrayModel(field=None)
  68. with self.assertRaises(IntegrityError):
  69. instance.save()
  70. def test_nested(self):
  71. instance = NestedIntegerArrayModel(field=[[1, 2], [3, 4]])
  72. instance.save()
  73. loaded = NestedIntegerArrayModel.objects.get()
  74. self.assertEqual(instance.field, loaded.field)
  75. def test_other_array_types(self):
  76. instance = OtherTypesArrayModel(
  77. ips=['192.168.0.1', '::1'],
  78. uuids=[uuid.uuid4()],
  79. decimals=[decimal.Decimal(1.25), 1.75],
  80. tags=[Tag(1), Tag(2), Tag(3)],
  81. )
  82. instance.save()
  83. loaded = OtherTypesArrayModel.objects.get()
  84. self.assertEqual(instance.ips, loaded.ips)
  85. self.assertEqual(instance.uuids, loaded.uuids)
  86. self.assertEqual(instance.decimals, loaded.decimals)
  87. self.assertEqual(instance.tags, loaded.tags)
  88. def test_null_from_db_value_handling(self):
  89. instance = OtherTypesArrayModel.objects.create(
  90. ips=['192.168.0.1', '::1'],
  91. uuids=[uuid.uuid4()],
  92. decimals=[decimal.Decimal(1.25), 1.75],
  93. tags=None,
  94. )
  95. instance.refresh_from_db()
  96. self.assertIsNone(instance.tags)
  97. def test_model_set_on_base_field(self):
  98. instance = IntegerArrayModel()
  99. field = instance._meta.get_field('field')
  100. self.assertEqual(field.model, IntegerArrayModel)
  101. self.assertEqual(field.base_field.model, IntegerArrayModel)
  102. class TestQuerying(PostgreSQLTestCase):
  103. def setUp(self):
  104. self.objs = [
  105. NullableIntegerArrayModel.objects.create(field=[1]),
  106. NullableIntegerArrayModel.objects.create(field=[2]),
  107. NullableIntegerArrayModel.objects.create(field=[2, 3]),
  108. NullableIntegerArrayModel.objects.create(field=[20, 30, 40]),
  109. NullableIntegerArrayModel.objects.create(field=None),
  110. ]
  111. def test_exact(self):
  112. self.assertSequenceEqual(
  113. NullableIntegerArrayModel.objects.filter(field__exact=[1]),
  114. self.objs[:1]
  115. )
  116. def test_exact_charfield(self):
  117. instance = CharArrayModel.objects.create(field=['text'])
  118. self.assertSequenceEqual(
  119. CharArrayModel.objects.filter(field=['text']),
  120. [instance]
  121. )
  122. def test_exact_nested(self):
  123. instance = NestedIntegerArrayModel.objects.create(field=[[1, 2], [3, 4]])
  124. self.assertSequenceEqual(
  125. NestedIntegerArrayModel.objects.filter(field=[[1, 2], [3, 4]]),
  126. [instance]
  127. )
  128. def test_isnull(self):
  129. self.assertSequenceEqual(
  130. NullableIntegerArrayModel.objects.filter(field__isnull=True),
  131. self.objs[-1:]
  132. )
  133. def test_gt(self):
  134. self.assertSequenceEqual(
  135. NullableIntegerArrayModel.objects.filter(field__gt=[0]),
  136. self.objs[:4]
  137. )
  138. def test_lt(self):
  139. self.assertSequenceEqual(
  140. NullableIntegerArrayModel.objects.filter(field__lt=[2]),
  141. self.objs[:1]
  142. )
  143. def test_in(self):
  144. self.assertSequenceEqual(
  145. NullableIntegerArrayModel.objects.filter(field__in=[[1], [2]]),
  146. self.objs[:2]
  147. )
  148. def test_contained_by(self):
  149. self.assertSequenceEqual(
  150. NullableIntegerArrayModel.objects.filter(field__contained_by=[1, 2]),
  151. self.objs[:2]
  152. )
  153. def test_contains(self):
  154. self.assertSequenceEqual(
  155. NullableIntegerArrayModel.objects.filter(field__contains=[2]),
  156. self.objs[1:3]
  157. )
  158. def test_contains_charfield(self):
  159. # Regression for #22907
  160. self.assertSequenceEqual(
  161. CharArrayModel.objects.filter(field__contains=['text']),
  162. []
  163. )
  164. def test_contained_by_charfield(self):
  165. self.assertSequenceEqual(
  166. CharArrayModel.objects.filter(field__contained_by=['text']),
  167. []
  168. )
  169. def test_overlap_charfield(self):
  170. self.assertSequenceEqual(
  171. CharArrayModel.objects.filter(field__overlap=['text']),
  172. []
  173. )
  174. def test_index(self):
  175. self.assertSequenceEqual(
  176. NullableIntegerArrayModel.objects.filter(field__0=2),
  177. self.objs[1:3]
  178. )
  179. def test_index_chained(self):
  180. self.assertSequenceEqual(
  181. NullableIntegerArrayModel.objects.filter(field__0__lt=3),
  182. self.objs[0:3]
  183. )
  184. def test_index_nested(self):
  185. instance = NestedIntegerArrayModel.objects.create(field=[[1, 2], [3, 4]])
  186. self.assertSequenceEqual(
  187. NestedIntegerArrayModel.objects.filter(field__0__0=1),
  188. [instance]
  189. )
  190. @unittest.expectedFailure
  191. def test_index_used_on_nested_data(self):
  192. instance = NestedIntegerArrayModel.objects.create(field=[[1, 2], [3, 4]])
  193. self.assertSequenceEqual(
  194. NestedIntegerArrayModel.objects.filter(field__0=[1, 2]),
  195. [instance]
  196. )
  197. def test_overlap(self):
  198. self.assertSequenceEqual(
  199. NullableIntegerArrayModel.objects.filter(field__overlap=[1, 2]),
  200. self.objs[0:3]
  201. )
  202. def test_len(self):
  203. self.assertSequenceEqual(
  204. NullableIntegerArrayModel.objects.filter(field__len__lte=2),
  205. self.objs[0:3]
  206. )
  207. def test_len_empty_array(self):
  208. obj = NullableIntegerArrayModel.objects.create(field=[])
  209. self.assertSequenceEqual(
  210. NullableIntegerArrayModel.objects.filter(field__len=0),
  211. [obj]
  212. )
  213. def test_slice(self):
  214. self.assertSequenceEqual(
  215. NullableIntegerArrayModel.objects.filter(field__0_1=[2]),
  216. self.objs[1:3]
  217. )
  218. self.assertSequenceEqual(
  219. NullableIntegerArrayModel.objects.filter(field__0_2=[2, 3]),
  220. self.objs[2:3]
  221. )
  222. @unittest.expectedFailure
  223. def test_slice_nested(self):
  224. instance = NestedIntegerArrayModel.objects.create(field=[[1, 2], [3, 4]])
  225. self.assertSequenceEqual(
  226. NestedIntegerArrayModel.objects.filter(field__0__0_1=[1]),
  227. [instance]
  228. )
  229. def test_usage_in_subquery(self):
  230. self.assertSequenceEqual(
  231. NullableIntegerArrayModel.objects.filter(
  232. id__in=NullableIntegerArrayModel.objects.filter(field__len=3)
  233. ),
  234. [self.objs[3]]
  235. )
  236. class TestDateTimeExactQuerying(PostgreSQLTestCase):
  237. def setUp(self):
  238. now = timezone.now()
  239. self.datetimes = [now]
  240. self.dates = [now.date()]
  241. self.times = [now.time()]
  242. self.objs = [
  243. DateTimeArrayModel.objects.create(
  244. datetimes=self.datetimes,
  245. dates=self.dates,
  246. times=self.times,
  247. )
  248. ]
  249. def test_exact_datetimes(self):
  250. self.assertSequenceEqual(
  251. DateTimeArrayModel.objects.filter(datetimes=self.datetimes),
  252. self.objs
  253. )
  254. def test_exact_dates(self):
  255. self.assertSequenceEqual(
  256. DateTimeArrayModel.objects.filter(dates=self.dates),
  257. self.objs
  258. )
  259. def test_exact_times(self):
  260. self.assertSequenceEqual(
  261. DateTimeArrayModel.objects.filter(times=self.times),
  262. self.objs
  263. )
  264. class TestOtherTypesExactQuerying(PostgreSQLTestCase):
  265. def setUp(self):
  266. self.ips = ['192.168.0.1', '::1']
  267. self.uuids = [uuid.uuid4()]
  268. self.decimals = [decimal.Decimal(1.25), 1.75]
  269. self.tags = [Tag(1), Tag(2), Tag(3)]
  270. self.objs = [
  271. OtherTypesArrayModel.objects.create(
  272. ips=self.ips,
  273. uuids=self.uuids,
  274. decimals=self.decimals,
  275. tags=self.tags,
  276. )
  277. ]
  278. def test_exact_ip_addresses(self):
  279. self.assertSequenceEqual(
  280. OtherTypesArrayModel.objects.filter(ips=self.ips),
  281. self.objs
  282. )
  283. def test_exact_uuids(self):
  284. self.assertSequenceEqual(
  285. OtherTypesArrayModel.objects.filter(uuids=self.uuids),
  286. self.objs
  287. )
  288. def test_exact_decimals(self):
  289. self.assertSequenceEqual(
  290. OtherTypesArrayModel.objects.filter(decimals=self.decimals),
  291. self.objs
  292. )
  293. def test_exact_tags(self):
  294. self.assertSequenceEqual(
  295. OtherTypesArrayModel.objects.filter(tags=self.tags),
  296. self.objs
  297. )
  298. @isolate_apps('postgres_tests')
  299. class TestChecks(PostgreSQLTestCase):
  300. def test_field_checks(self):
  301. class MyModel(PostgreSQLModel):
  302. field = ArrayField(models.CharField())
  303. model = MyModel()
  304. errors = model.check()
  305. self.assertEqual(len(errors), 1)
  306. # The inner CharField is missing a max_length.
  307. self.assertEqual(errors[0].id, 'postgres.E001')
  308. self.assertIn('max_length', errors[0].msg)
  309. def test_invalid_base_fields(self):
  310. class MyModel(PostgreSQLModel):
  311. field = ArrayField(models.ManyToManyField('postgres_tests.IntegerArrayModel'))
  312. model = MyModel()
  313. errors = model.check()
  314. self.assertEqual(len(errors), 1)
  315. self.assertEqual(errors[0].id, 'postgres.E002')
  316. def test_nested_field_checks(self):
  317. """
  318. Nested ArrayFields are permitted.
  319. """
  320. class MyModel(PostgreSQLModel):
  321. field = ArrayField(ArrayField(models.CharField()))
  322. model = MyModel()
  323. errors = model.check()
  324. self.assertEqual(len(errors), 1)
  325. # The inner CharField is missing a max_length.
  326. self.assertEqual(errors[0].id, 'postgres.E001')
  327. self.assertIn('max_length', errors[0].msg)
  328. @unittest.skipUnless(connection.vendor == 'postgresql', "PostgreSQL specific tests")
  329. class TestMigrations(TransactionTestCase):
  330. available_apps = ['postgres_tests']
  331. def test_deconstruct(self):
  332. field = ArrayField(models.IntegerField())
  333. name, path, args, kwargs = field.deconstruct()
  334. new = ArrayField(*args, **kwargs)
  335. self.assertEqual(type(new.base_field), type(field.base_field))
  336. def test_deconstruct_with_size(self):
  337. field = ArrayField(models.IntegerField(), size=3)
  338. name, path, args, kwargs = field.deconstruct()
  339. new = ArrayField(*args, **kwargs)
  340. self.assertEqual(new.size, field.size)
  341. def test_deconstruct_args(self):
  342. field = ArrayField(models.CharField(max_length=20))
  343. name, path, args, kwargs = field.deconstruct()
  344. new = ArrayField(*args, **kwargs)
  345. self.assertEqual(new.base_field.max_length, field.base_field.max_length)
  346. def test_subclass_deconstruct(self):
  347. field = ArrayField(models.IntegerField())
  348. name, path, args, kwargs = field.deconstruct()
  349. self.assertEqual(path, 'django.contrib.postgres.fields.ArrayField')
  350. field = ArrayFieldSubclass()
  351. name, path, args, kwargs = field.deconstruct()
  352. self.assertEqual(path, 'postgres_tests.models.ArrayFieldSubclass')
  353. @override_settings(MIGRATION_MODULES={
  354. "postgres_tests": "postgres_tests.array_default_migrations",
  355. })
  356. def test_adding_field_with_default(self):
  357. # See #22962
  358. table_name = 'postgres_tests_integerarraydefaultmodel'
  359. with connection.cursor() as cursor:
  360. self.assertNotIn(table_name, connection.introspection.table_names(cursor))
  361. call_command('migrate', 'postgres_tests', verbosity=0)
  362. with connection.cursor() as cursor:
  363. self.assertIn(table_name, connection.introspection.table_names(cursor))
  364. call_command('migrate', 'postgres_tests', 'zero', verbosity=0)
  365. with connection.cursor() as cursor:
  366. self.assertNotIn(table_name, connection.introspection.table_names(cursor))
  367. @override_settings(MIGRATION_MODULES={
  368. "postgres_tests": "postgres_tests.array_index_migrations",
  369. })
  370. def test_adding_arrayfield_with_index(self):
  371. """
  372. ArrayField shouldn't have varchar_patterns_ops or text_patterns_ops indexes.
  373. """
  374. table_name = 'postgres_tests_chartextarrayindexmodel'
  375. call_command('migrate', 'postgres_tests', verbosity=0)
  376. with connection.cursor() as cursor:
  377. like_constraint_field_names = [
  378. c.rsplit('_', 2)[0][len(table_name) + 1:]
  379. for c in connection.introspection.get_constraints(cursor, table_name)
  380. if c.endswith('_like')
  381. ]
  382. # Only the CharField should have a LIKE index.
  383. self.assertEqual(like_constraint_field_names, ['char2'])
  384. with connection.cursor() as cursor:
  385. indexes = connection.introspection.get_indexes(cursor, table_name)
  386. # All fields should have regular indexes.
  387. self.assertIn('char', indexes)
  388. self.assertIn('char2', indexes)
  389. self.assertIn('text', indexes)
  390. call_command('migrate', 'postgres_tests', 'zero', verbosity=0)
  391. with connection.cursor() as cursor:
  392. self.assertNotIn(table_name, connection.introspection.table_names(cursor))
  393. class TestSerialization(PostgreSQLTestCase):
  394. test_data = (
  395. '[{"fields": {"field": "[\\"1\\", \\"2\\", null]"}, "model": "postgres_tests.integerarraymodel", "pk": null}]'
  396. )
  397. def test_dumping(self):
  398. instance = IntegerArrayModel(field=[1, 2, None])
  399. data = serializers.serialize('json', [instance])
  400. self.assertEqual(json.loads(data), json.loads(self.test_data))
  401. def test_loading(self):
  402. instance = list(serializers.deserialize('json', self.test_data))[0].object
  403. self.assertEqual(instance.field, [1, 2, None])
  404. class TestValidation(PostgreSQLTestCase):
  405. def test_unbounded(self):
  406. field = ArrayField(models.IntegerField())
  407. with self.assertRaises(exceptions.ValidationError) as cm:
  408. field.clean([1, None], None)
  409. self.assertEqual(cm.exception.code, 'item_invalid')
  410. self.assertEqual(
  411. cm.exception.message % cm.exception.params,
  412. 'Item 1 in the array did not validate: This field cannot be null.'
  413. )
  414. def test_blank_true(self):
  415. field = ArrayField(models.IntegerField(blank=True, null=True))
  416. # This should not raise a validation error
  417. field.clean([1, None], None)
  418. def test_with_size(self):
  419. field = ArrayField(models.IntegerField(), size=3)
  420. field.clean([1, 2, 3], None)
  421. with self.assertRaises(exceptions.ValidationError) as cm:
  422. field.clean([1, 2, 3, 4], None)
  423. self.assertEqual(cm.exception.messages[0], 'List contains 4 items, it should contain no more than 3.')
  424. def test_nested_array_mismatch(self):
  425. field = ArrayField(ArrayField(models.IntegerField()))
  426. field.clean([[1, 2], [3, 4]], None)
  427. with self.assertRaises(exceptions.ValidationError) as cm:
  428. field.clean([[1, 2], [3, 4, 5]], None)
  429. self.assertEqual(cm.exception.code, 'nested_array_mismatch')
  430. self.assertEqual(cm.exception.messages[0], 'Nested arrays must have the same length.')
  431. def test_with_base_field_error_params(self):
  432. field = ArrayField(models.CharField(max_length=2))
  433. with self.assertRaises(exceptions.ValidationError) as cm:
  434. field.clean(['abc'], None)
  435. self.assertEqual(len(cm.exception.error_list), 1)
  436. exception = cm.exception.error_list[0]
  437. self.assertEqual(
  438. exception.message,
  439. 'Item 0 in the array did not validate: Ensure this value has at most 2 characters (it has 3).'
  440. )
  441. self.assertEqual(exception.code, 'item_invalid')
  442. self.assertEqual(exception.params, {'nth': 0, 'value': 'abc', 'limit_value': 2, 'show_value': 3})
  443. def test_with_validators(self):
  444. field = ArrayField(models.IntegerField(validators=[validators.MinValueValidator(1)]))
  445. field.clean([1, 2], None)
  446. with self.assertRaises(exceptions.ValidationError) as cm:
  447. field.clean([0], None)
  448. self.assertEqual(len(cm.exception.error_list), 1)
  449. exception = cm.exception.error_list[0]
  450. self.assertEqual(
  451. exception.message,
  452. 'Item 0 in the array did not validate: Ensure this value is greater than or equal to 1.'
  453. )
  454. self.assertEqual(exception.code, 'item_invalid')
  455. self.assertEqual(exception.params, {'nth': 0, 'value': 0, 'limit_value': 1, 'show_value': 0})
  456. class TestSimpleFormField(PostgreSQLTestCase):
  457. def test_valid(self):
  458. field = SimpleArrayField(forms.CharField())
  459. value = field.clean('a,b,c')
  460. self.assertEqual(value, ['a', 'b', 'c'])
  461. def test_to_python_fail(self):
  462. field = SimpleArrayField(forms.IntegerField())
  463. with self.assertRaises(exceptions.ValidationError) as cm:
  464. field.clean('a,b,9')
  465. self.assertEqual(cm.exception.messages[0], 'Item 0 in the array did not validate: Enter a whole number.')
  466. def test_validate_fail(self):
  467. field = SimpleArrayField(forms.CharField(required=True))
  468. with self.assertRaises(exceptions.ValidationError) as cm:
  469. field.clean('a,b,')
  470. self.assertEqual(cm.exception.messages[0], 'Item 2 in the array did not validate: This field is required.')
  471. def test_validate_fail_base_field_error_params(self):
  472. field = SimpleArrayField(forms.CharField(max_length=2))
  473. with self.assertRaises(exceptions.ValidationError) as cm:
  474. field.clean('abc,c,defg')
  475. errors = cm.exception.error_list
  476. self.assertEqual(len(errors), 2)
  477. first_error = errors[0]
  478. self.assertEqual(
  479. first_error.message,
  480. 'Item 0 in the array did not validate: Ensure this value has at most 2 characters (it has 3).'
  481. )
  482. self.assertEqual(first_error.code, 'item_invalid')
  483. self.assertEqual(first_error.params, {'nth': 0, 'value': 'abc', 'limit_value': 2, 'show_value': 3})
  484. second_error = errors[1]
  485. self.assertEqual(
  486. second_error.message,
  487. 'Item 2 in the array did not validate: Ensure this value has at most 2 characters (it has 4).'
  488. )
  489. self.assertEqual(second_error.code, 'item_invalid')
  490. self.assertEqual(second_error.params, {'nth': 2, 'value': 'defg', 'limit_value': 2, 'show_value': 4})
  491. def test_validators_fail(self):
  492. field = SimpleArrayField(forms.RegexField('[a-e]{2}'))
  493. with self.assertRaises(exceptions.ValidationError) as cm:
  494. field.clean('a,bc,de')
  495. self.assertEqual(cm.exception.messages[0], 'Item 0 in the array did not validate: Enter a valid value.')
  496. def test_delimiter(self):
  497. field = SimpleArrayField(forms.CharField(), delimiter='|')
  498. value = field.clean('a|b|c')
  499. self.assertEqual(value, ['a', 'b', 'c'])
  500. def test_delimiter_with_nesting(self):
  501. field = SimpleArrayField(SimpleArrayField(forms.CharField()), delimiter='|')
  502. value = field.clean('a,b|c,d')
  503. self.assertEqual(value, [['a', 'b'], ['c', 'd']])
  504. def test_prepare_value(self):
  505. field = SimpleArrayField(forms.CharField())
  506. value = field.prepare_value(['a', 'b', 'c'])
  507. self.assertEqual(value, 'a,b,c')
  508. def test_max_length(self):
  509. field = SimpleArrayField(forms.CharField(), max_length=2)
  510. with self.assertRaises(exceptions.ValidationError) as cm:
  511. field.clean('a,b,c')
  512. self.assertEqual(cm.exception.messages[0], 'List contains 3 items, it should contain no more than 2.')
  513. def test_min_length(self):
  514. field = SimpleArrayField(forms.CharField(), min_length=4)
  515. with self.assertRaises(exceptions.ValidationError) as cm:
  516. field.clean('a,b,c')
  517. self.assertEqual(cm.exception.messages[0], 'List contains 3 items, it should contain no fewer than 4.')
  518. def test_required(self):
  519. field = SimpleArrayField(forms.CharField(), required=True)
  520. with self.assertRaises(exceptions.ValidationError) as cm:
  521. field.clean('')
  522. self.assertEqual(cm.exception.messages[0], 'This field is required.')
  523. def test_model_field_formfield(self):
  524. model_field = ArrayField(models.CharField(max_length=27))
  525. form_field = model_field.formfield()
  526. self.assertIsInstance(form_field, SimpleArrayField)
  527. self.assertIsInstance(form_field.base_field, forms.CharField)
  528. self.assertEqual(form_field.base_field.max_length, 27)
  529. def test_model_field_formfield_size(self):
  530. model_field = ArrayField(models.CharField(max_length=27), size=4)
  531. form_field = model_field.formfield()
  532. self.assertIsInstance(form_field, SimpleArrayField)
  533. self.assertEqual(form_field.max_length, 4)
  534. class TestSplitFormField(PostgreSQLTestCase):
  535. def test_valid(self):
  536. class SplitForm(forms.Form):
  537. array = SplitArrayField(forms.CharField(), size=3)
  538. data = {'array_0': 'a', 'array_1': 'b', 'array_2': 'c'}
  539. form = SplitForm(data)
  540. self.assertTrue(form.is_valid())
  541. self.assertEqual(form.cleaned_data, {'array': ['a', 'b', 'c']})
  542. def test_required(self):
  543. class SplitForm(forms.Form):
  544. array = SplitArrayField(forms.CharField(), required=True, size=3)
  545. data = {'array_0': '', 'array_1': '', 'array_2': ''}
  546. form = SplitForm(data)
  547. self.assertFalse(form.is_valid())
  548. self.assertEqual(form.errors, {'array': ['This field is required.']})
  549. def test_remove_trailing_nulls(self):
  550. class SplitForm(forms.Form):
  551. array = SplitArrayField(forms.CharField(required=False), size=5, remove_trailing_nulls=True)
  552. data = {'array_0': 'a', 'array_1': '', 'array_2': 'b', 'array_3': '', 'array_4': ''}
  553. form = SplitForm(data)
  554. self.assertTrue(form.is_valid(), form.errors)
  555. self.assertEqual(form.cleaned_data, {'array': ['a', '', 'b']})
  556. def test_remove_trailing_nulls_not_required(self):
  557. class SplitForm(forms.Form):
  558. array = SplitArrayField(
  559. forms.CharField(required=False),
  560. size=2,
  561. remove_trailing_nulls=True,
  562. required=False,
  563. )
  564. data = {'array_0': '', 'array_1': ''}
  565. form = SplitForm(data)
  566. self.assertTrue(form.is_valid())
  567. self.assertEqual(form.cleaned_data, {'array': []})
  568. def test_required_field(self):
  569. class SplitForm(forms.Form):
  570. array = SplitArrayField(forms.CharField(), size=3)
  571. data = {'array_0': 'a', 'array_1': 'b', 'array_2': ''}
  572. form = SplitForm(data)
  573. self.assertFalse(form.is_valid())
  574. self.assertEqual(form.errors, {'array': ['Item 2 in the array did not validate: This field is required.']})
  575. def test_invalid_integer(self):
  576. msg = 'Item 1 in the array did not validate: Ensure this value is less than or equal to 100.'
  577. with self.assertRaisesMessage(exceptions.ValidationError, msg):
  578. SplitArrayField(forms.IntegerField(max_value=100), size=2).clean([0, 101])
  579. def test_rendering(self):
  580. class SplitForm(forms.Form):
  581. array = SplitArrayField(forms.CharField(), size=3)
  582. self.assertHTMLEqual(str(SplitForm()), '''
  583. <tr>
  584. <th><label for="id_array_0">Array:</label></th>
  585. <td>
  586. <input id="id_array_0" name="array_0" type="text" required />
  587. <input id="id_array_1" name="array_1" type="text" required />
  588. <input id="id_array_2" name="array_2" type="text" required />
  589. </td>
  590. </tr>
  591. ''')
  592. def test_invalid_char_length(self):
  593. field = SplitArrayField(forms.CharField(max_length=2), size=3)
  594. with self.assertRaises(exceptions.ValidationError) as cm:
  595. field.clean(['abc', 'c', 'defg'])
  596. self.assertEqual(cm.exception.messages, [
  597. 'Item 0 in the array did not validate: Ensure this value has at most 2 characters (it has 3).',
  598. 'Item 2 in the array did not validate: Ensure this value has at most 2 characters (it has 4).',
  599. ])