test_ranges.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634
  1. import datetime
  2. import json
  3. import unittest
  4. from django import forms
  5. from django.core import exceptions, serializers
  6. from django.db import connection
  7. from django.db.models import F
  8. from django.test import TestCase, override_settings
  9. from django.utils import timezone
  10. from . import PostgreSQLTestCase
  11. from .models import RangeLookupsModel, RangesModel
  12. try:
  13. from psycopg2.extras import DateRange, DateTimeTZRange, NumericRange
  14. from django.contrib.postgres import fields as pg_fields, forms as pg_forms
  15. from django.contrib.postgres.validators import (
  16. RangeMaxValueValidator, RangeMinValueValidator,
  17. )
  18. except ImportError:
  19. pass
  20. def skipUnlessPG92(test):
  21. try:
  22. PG_VERSION = connection.pg_version
  23. except AttributeError:
  24. PG_VERSION = 0
  25. if PG_VERSION < 90200:
  26. return unittest.skip('PostgreSQL >= 9.2 required')(test)
  27. return test
  28. @skipUnlessPG92
  29. class TestSaveLoad(TestCase):
  30. def test_all_fields(self):
  31. now = timezone.now()
  32. instance = RangesModel(
  33. ints=NumericRange(0, 10),
  34. bigints=NumericRange(10, 20),
  35. floats=NumericRange(20, 30),
  36. timestamps=DateTimeTZRange(now - datetime.timedelta(hours=1), now),
  37. dates=DateRange(now.date() - datetime.timedelta(days=1), now.date()),
  38. )
  39. instance.save()
  40. loaded = RangesModel.objects.get()
  41. self.assertEqual(instance.ints, loaded.ints)
  42. self.assertEqual(instance.bigints, loaded.bigints)
  43. self.assertEqual(instance.floats, loaded.floats)
  44. self.assertEqual(instance.timestamps, loaded.timestamps)
  45. self.assertEqual(instance.dates, loaded.dates)
  46. def test_range_object(self):
  47. r = NumericRange(0, 10)
  48. instance = RangesModel(ints=r)
  49. instance.save()
  50. loaded = RangesModel.objects.get()
  51. self.assertEqual(r, loaded.ints)
  52. def test_tuple(self):
  53. instance = RangesModel(ints=(0, 10))
  54. instance.save()
  55. loaded = RangesModel.objects.get()
  56. self.assertEqual(NumericRange(0, 10), loaded.ints)
  57. def test_range_object_boundaries(self):
  58. r = NumericRange(0, 10, '[]')
  59. instance = RangesModel(floats=r)
  60. instance.save()
  61. loaded = RangesModel.objects.get()
  62. self.assertEqual(r, loaded.floats)
  63. self.assertTrue(10 in loaded.floats)
  64. def test_unbounded(self):
  65. r = NumericRange(None, None, '()')
  66. instance = RangesModel(floats=r)
  67. instance.save()
  68. loaded = RangesModel.objects.get()
  69. self.assertEqual(r, loaded.floats)
  70. def test_empty(self):
  71. r = NumericRange(empty=True)
  72. instance = RangesModel(ints=r)
  73. instance.save()
  74. loaded = RangesModel.objects.get()
  75. self.assertEqual(r, loaded.ints)
  76. def test_null(self):
  77. instance = RangesModel(ints=None)
  78. instance.save()
  79. loaded = RangesModel.objects.get()
  80. self.assertIsNone(loaded.ints)
  81. @skipUnlessPG92
  82. class TestQuerying(TestCase):
  83. @classmethod
  84. def setUpTestData(cls):
  85. cls.objs = [
  86. RangesModel.objects.create(ints=NumericRange(0, 10)),
  87. RangesModel.objects.create(ints=NumericRange(5, 15)),
  88. RangesModel.objects.create(ints=NumericRange(None, 0)),
  89. RangesModel.objects.create(ints=NumericRange(empty=True)),
  90. RangesModel.objects.create(ints=None),
  91. ]
  92. def test_exact(self):
  93. self.assertSequenceEqual(
  94. RangesModel.objects.filter(ints__exact=NumericRange(0, 10)),
  95. [self.objs[0]],
  96. )
  97. def test_isnull(self):
  98. self.assertSequenceEqual(
  99. RangesModel.objects.filter(ints__isnull=True),
  100. [self.objs[4]],
  101. )
  102. def test_isempty(self):
  103. self.assertSequenceEqual(
  104. RangesModel.objects.filter(ints__isempty=True),
  105. [self.objs[3]],
  106. )
  107. def test_contains(self):
  108. self.assertSequenceEqual(
  109. RangesModel.objects.filter(ints__contains=8),
  110. [self.objs[0], self.objs[1]],
  111. )
  112. def test_contains_range(self):
  113. self.assertSequenceEqual(
  114. RangesModel.objects.filter(ints__contains=NumericRange(3, 8)),
  115. [self.objs[0]],
  116. )
  117. def test_contained_by(self):
  118. self.assertSequenceEqual(
  119. RangesModel.objects.filter(ints__contained_by=NumericRange(0, 20)),
  120. [self.objs[0], self.objs[1], self.objs[3]],
  121. )
  122. def test_overlap(self):
  123. self.assertSequenceEqual(
  124. RangesModel.objects.filter(ints__overlap=NumericRange(3, 8)),
  125. [self.objs[0], self.objs[1]],
  126. )
  127. def test_fully_lt(self):
  128. self.assertSequenceEqual(
  129. RangesModel.objects.filter(ints__fully_lt=NumericRange(5, 10)),
  130. [self.objs[2]],
  131. )
  132. def test_fully_gt(self):
  133. self.assertSequenceEqual(
  134. RangesModel.objects.filter(ints__fully_gt=NumericRange(5, 10)),
  135. [],
  136. )
  137. def test_not_lt(self):
  138. self.assertSequenceEqual(
  139. RangesModel.objects.filter(ints__not_lt=NumericRange(5, 10)),
  140. [self.objs[1]],
  141. )
  142. def test_not_gt(self):
  143. self.assertSequenceEqual(
  144. RangesModel.objects.filter(ints__not_gt=NumericRange(5, 10)),
  145. [self.objs[0], self.objs[2]],
  146. )
  147. def test_adjacent_to(self):
  148. self.assertSequenceEqual(
  149. RangesModel.objects.filter(ints__adjacent_to=NumericRange(0, 5)),
  150. [self.objs[1], self.objs[2]],
  151. )
  152. def test_startswith(self):
  153. self.assertSequenceEqual(
  154. RangesModel.objects.filter(ints__startswith=0),
  155. [self.objs[0]],
  156. )
  157. def test_endswith(self):
  158. self.assertSequenceEqual(
  159. RangesModel.objects.filter(ints__endswith=0),
  160. [self.objs[2]],
  161. )
  162. def test_startswith_chaining(self):
  163. self.assertSequenceEqual(
  164. RangesModel.objects.filter(ints__startswith__gte=0),
  165. [self.objs[0], self.objs[1]],
  166. )
  167. @skipUnlessPG92
  168. class TestQueryingWithRanges(TestCase):
  169. def test_date_range(self):
  170. objs = [
  171. RangeLookupsModel.objects.create(date='2015-01-01'),
  172. RangeLookupsModel.objects.create(date='2015-05-05'),
  173. ]
  174. self.assertSequenceEqual(
  175. RangeLookupsModel.objects.filter(date__contained_by=DateRange('2015-01-01', '2015-05-04')),
  176. [objs[0]],
  177. )
  178. def test_date_range_datetime_field(self):
  179. objs = [
  180. RangeLookupsModel.objects.create(timestamp='2015-01-01'),
  181. RangeLookupsModel.objects.create(timestamp='2015-05-05'),
  182. ]
  183. self.assertSequenceEqual(
  184. RangeLookupsModel.objects.filter(timestamp__date__contained_by=DateRange('2015-01-01', '2015-05-04')),
  185. [objs[0]],
  186. )
  187. def test_datetime_range(self):
  188. objs = [
  189. RangeLookupsModel.objects.create(timestamp='2015-01-01T09:00:00'),
  190. RangeLookupsModel.objects.create(timestamp='2015-05-05T17:00:00'),
  191. ]
  192. self.assertSequenceEqual(
  193. RangeLookupsModel.objects.filter(
  194. timestamp__contained_by=DateTimeTZRange('2015-01-01T09:00', '2015-05-04T23:55')
  195. ),
  196. [objs[0]],
  197. )
  198. def test_integer_range(self):
  199. objs = [
  200. RangeLookupsModel.objects.create(integer=5),
  201. RangeLookupsModel.objects.create(integer=99),
  202. RangeLookupsModel.objects.create(integer=-1),
  203. ]
  204. self.assertSequenceEqual(
  205. RangeLookupsModel.objects.filter(integer__contained_by=NumericRange(1, 98)),
  206. [objs[0]]
  207. )
  208. def test_biginteger_range(self):
  209. objs = [
  210. RangeLookupsModel.objects.create(big_integer=5),
  211. RangeLookupsModel.objects.create(big_integer=99),
  212. RangeLookupsModel.objects.create(big_integer=-1),
  213. ]
  214. self.assertSequenceEqual(
  215. RangeLookupsModel.objects.filter(big_integer__contained_by=NumericRange(1, 98)),
  216. [objs[0]]
  217. )
  218. def test_float_range(self):
  219. objs = [
  220. RangeLookupsModel.objects.create(float=5),
  221. RangeLookupsModel.objects.create(float=99),
  222. RangeLookupsModel.objects.create(float=-1),
  223. ]
  224. self.assertSequenceEqual(
  225. RangeLookupsModel.objects.filter(float__contained_by=NumericRange(1, 98)),
  226. [objs[0]]
  227. )
  228. def test_f_ranges(self):
  229. parent = RangesModel.objects.create(floats=NumericRange(0, 10))
  230. objs = [
  231. RangeLookupsModel.objects.create(float=5, parent=parent),
  232. RangeLookupsModel.objects.create(float=99, parent=parent),
  233. ]
  234. self.assertSequenceEqual(
  235. RangeLookupsModel.objects.filter(float__contained_by=F('parent__floats')),
  236. [objs[0]]
  237. )
  238. def test_exclude(self):
  239. objs = [
  240. RangeLookupsModel.objects.create(float=5),
  241. RangeLookupsModel.objects.create(float=99),
  242. RangeLookupsModel.objects.create(float=-1),
  243. ]
  244. self.assertSequenceEqual(
  245. RangeLookupsModel.objects.exclude(float__contained_by=NumericRange(0, 100)),
  246. [objs[2]]
  247. )
  248. @skipUnlessPG92
  249. class TestSerialization(TestCase):
  250. test_data = (
  251. '[{"fields": {"ints": "{\\"upper\\": \\"10\\", \\"lower\\": \\"0\\", '
  252. '\\"bounds\\": \\"[)\\"}", "floats": "{\\"empty\\": true}", '
  253. '"bigints": null, "timestamps": "{\\"upper\\": \\"2014-02-02T12:12:12+00:00\\", '
  254. '\\"lower\\": \\"2014-01-01T00:00:00+00:00\\", \\"bounds\\": \\"[)\\"}", '
  255. '"dates": "{\\"upper\\": \\"2014-02-02\\", \\"lower\\": \\"2014-01-01\\", \\"bounds\\": \\"[)\\"}" }, '
  256. '"model": "postgres_tests.rangesmodel", "pk": null}]'
  257. )
  258. lower_date = datetime.date(2014, 1, 1)
  259. upper_date = datetime.date(2014, 2, 2)
  260. lower_dt = datetime.datetime(2014, 1, 1, 0, 0, 0, tzinfo=timezone.utc)
  261. upper_dt = datetime.datetime(2014, 2, 2, 12, 12, 12, tzinfo=timezone.utc)
  262. def test_dumping(self):
  263. instance = RangesModel(ints=NumericRange(0, 10), floats=NumericRange(empty=True),
  264. timestamps=DateTimeTZRange(self.lower_dt, self.upper_dt),
  265. dates=DateRange(self.lower_date, self.upper_date))
  266. data = serializers.serialize('json', [instance])
  267. dumped = json.loads(data)
  268. for field in ('ints', 'dates', 'timestamps'):
  269. dumped[0]['fields'][field] = json.loads(dumped[0]['fields'][field])
  270. check = json.loads(self.test_data)
  271. for field in ('ints', 'dates', 'timestamps'):
  272. check[0]['fields'][field] = json.loads(check[0]['fields'][field])
  273. self.assertEqual(dumped, check)
  274. def test_loading(self):
  275. instance = list(serializers.deserialize('json', self.test_data))[0].object
  276. self.assertEqual(instance.ints, NumericRange(0, 10))
  277. self.assertEqual(instance.floats, NumericRange(empty=True))
  278. self.assertEqual(instance.bigints, None)
  279. self.assertEqual(instance.dates, DateRange(self.lower_date, self.upper_date))
  280. self.assertEqual(instance.timestamps, DateTimeTZRange(self.lower_dt, self.upper_dt))
  281. class TestValidators(PostgreSQLTestCase):
  282. def test_max(self):
  283. validator = RangeMaxValueValidator(5)
  284. validator(NumericRange(0, 5))
  285. with self.assertRaises(exceptions.ValidationError) as cm:
  286. validator(NumericRange(0, 10))
  287. self.assertEqual(cm.exception.messages[0], 'Ensure that this range is completely less than or equal to 5.')
  288. self.assertEqual(cm.exception.code, 'max_value')
  289. def test_min(self):
  290. validator = RangeMinValueValidator(5)
  291. validator(NumericRange(10, 15))
  292. with self.assertRaises(exceptions.ValidationError) as cm:
  293. validator(NumericRange(0, 10))
  294. self.assertEqual(cm.exception.messages[0], 'Ensure that this range is completely greater than or equal to 5.')
  295. self.assertEqual(cm.exception.code, 'min_value')
  296. class TestFormField(PostgreSQLTestCase):
  297. def test_valid_integer(self):
  298. field = pg_forms.IntegerRangeField()
  299. value = field.clean(['1', '2'])
  300. self.assertEqual(value, NumericRange(1, 2))
  301. def test_valid_floats(self):
  302. field = pg_forms.FloatRangeField()
  303. value = field.clean(['1.12345', '2.001'])
  304. self.assertEqual(value, NumericRange(1.12345, 2.001))
  305. def test_valid_timestamps(self):
  306. field = pg_forms.DateTimeRangeField()
  307. value = field.clean(['01/01/2014 00:00:00', '02/02/2014 12:12:12'])
  308. lower = datetime.datetime(2014, 1, 1, 0, 0, 0)
  309. upper = datetime.datetime(2014, 2, 2, 12, 12, 12)
  310. self.assertEqual(value, DateTimeTZRange(lower, upper))
  311. def test_valid_dates(self):
  312. field = pg_forms.DateRangeField()
  313. value = field.clean(['01/01/2014', '02/02/2014'])
  314. lower = datetime.date(2014, 1, 1)
  315. upper = datetime.date(2014, 2, 2)
  316. self.assertEqual(value, DateRange(lower, upper))
  317. def test_using_split_datetime_widget(self):
  318. class SplitDateTimeRangeField(pg_forms.DateTimeRangeField):
  319. base_field = forms.SplitDateTimeField
  320. class SplitForm(forms.Form):
  321. field = SplitDateTimeRangeField()
  322. form = SplitForm()
  323. self.assertHTMLEqual(str(form), '''
  324. <tr>
  325. <th>
  326. <label for="id_field_0">Field:</label>
  327. </th>
  328. <td>
  329. <input id="id_field_0_0" name="field_0_0" type="text" />
  330. <input id="id_field_0_1" name="field_0_1" type="text" />
  331. <input id="id_field_1_0" name="field_1_0" type="text" />
  332. <input id="id_field_1_1" name="field_1_1" type="text" />
  333. </td>
  334. </tr>
  335. ''')
  336. form = SplitForm({
  337. 'field_0_0': '01/01/2014',
  338. 'field_0_1': '00:00:00',
  339. 'field_1_0': '02/02/2014',
  340. 'field_1_1': '12:12:12',
  341. })
  342. self.assertTrue(form.is_valid())
  343. lower = datetime.datetime(2014, 1, 1, 0, 0, 0)
  344. upper = datetime.datetime(2014, 2, 2, 12, 12, 12)
  345. self.assertEqual(form.cleaned_data['field'], DateTimeTZRange(lower, upper))
  346. def test_none(self):
  347. field = pg_forms.IntegerRangeField(required=False)
  348. value = field.clean(['', ''])
  349. self.assertEqual(value, None)
  350. def test_rendering(self):
  351. class RangeForm(forms.Form):
  352. ints = pg_forms.IntegerRangeField()
  353. self.assertHTMLEqual(str(RangeForm()), '''
  354. <tr>
  355. <th><label for="id_ints_0">Ints:</label></th>
  356. <td>
  357. <input id="id_ints_0" name="ints_0" type="number" />
  358. <input id="id_ints_1" name="ints_1" type="number" />
  359. </td>
  360. </tr>
  361. ''')
  362. def test_integer_lower_bound_higher(self):
  363. field = pg_forms.IntegerRangeField()
  364. with self.assertRaises(exceptions.ValidationError) as cm:
  365. field.clean(['10', '2'])
  366. self.assertEqual(cm.exception.messages[0], 'The start of the range must not exceed the end of the range.')
  367. self.assertEqual(cm.exception.code, 'bound_ordering')
  368. def test_integer_open(self):
  369. field = pg_forms.IntegerRangeField()
  370. value = field.clean(['', '0'])
  371. self.assertEqual(value, NumericRange(None, 0))
  372. def test_integer_incorrect_data_type(self):
  373. field = pg_forms.IntegerRangeField()
  374. with self.assertRaises(exceptions.ValidationError) as cm:
  375. field.clean('1')
  376. self.assertEqual(cm.exception.messages[0], 'Enter two whole numbers.')
  377. self.assertEqual(cm.exception.code, 'invalid')
  378. def test_integer_invalid_lower(self):
  379. field = pg_forms.IntegerRangeField()
  380. with self.assertRaises(exceptions.ValidationError) as cm:
  381. field.clean(['a', '2'])
  382. self.assertEqual(cm.exception.messages[0], 'Enter a whole number.')
  383. def test_integer_invalid_upper(self):
  384. field = pg_forms.IntegerRangeField()
  385. with self.assertRaises(exceptions.ValidationError) as cm:
  386. field.clean(['1', 'b'])
  387. self.assertEqual(cm.exception.messages[0], 'Enter a whole number.')
  388. def test_integer_required(self):
  389. field = pg_forms.IntegerRangeField(required=True)
  390. with self.assertRaises(exceptions.ValidationError) as cm:
  391. field.clean(['', ''])
  392. self.assertEqual(cm.exception.messages[0], 'This field is required.')
  393. value = field.clean([1, ''])
  394. self.assertEqual(value, NumericRange(1, None))
  395. def test_float_lower_bound_higher(self):
  396. field = pg_forms.FloatRangeField()
  397. with self.assertRaises(exceptions.ValidationError) as cm:
  398. field.clean(['1.8', '1.6'])
  399. self.assertEqual(cm.exception.messages[0], 'The start of the range must not exceed the end of the range.')
  400. self.assertEqual(cm.exception.code, 'bound_ordering')
  401. def test_float_open(self):
  402. field = pg_forms.FloatRangeField()
  403. value = field.clean(['', '3.1415926'])
  404. self.assertEqual(value, NumericRange(None, 3.1415926))
  405. def test_float_incorrect_data_type(self):
  406. field = pg_forms.FloatRangeField()
  407. with self.assertRaises(exceptions.ValidationError) as cm:
  408. field.clean('1.6')
  409. self.assertEqual(cm.exception.messages[0], 'Enter two numbers.')
  410. self.assertEqual(cm.exception.code, 'invalid')
  411. def test_float_invalid_lower(self):
  412. field = pg_forms.FloatRangeField()
  413. with self.assertRaises(exceptions.ValidationError) as cm:
  414. field.clean(['a', '3.1415926'])
  415. self.assertEqual(cm.exception.messages[0], 'Enter a number.')
  416. def test_float_invalid_upper(self):
  417. field = pg_forms.FloatRangeField()
  418. with self.assertRaises(exceptions.ValidationError) as cm:
  419. field.clean(['1.61803399', 'b'])
  420. self.assertEqual(cm.exception.messages[0], 'Enter a number.')
  421. def test_float_required(self):
  422. field = pg_forms.FloatRangeField(required=True)
  423. with self.assertRaises(exceptions.ValidationError) as cm:
  424. field.clean(['', ''])
  425. self.assertEqual(cm.exception.messages[0], 'This field is required.')
  426. value = field.clean(['1.61803399', ''])
  427. self.assertEqual(value, NumericRange(1.61803399, None))
  428. def test_date_lower_bound_higher(self):
  429. field = pg_forms.DateRangeField()
  430. with self.assertRaises(exceptions.ValidationError) as cm:
  431. field.clean(['2013-04-09', '1976-04-16'])
  432. self.assertEqual(cm.exception.messages[0], 'The start of the range must not exceed the end of the range.')
  433. self.assertEqual(cm.exception.code, 'bound_ordering')
  434. def test_date_open(self):
  435. field = pg_forms.DateRangeField()
  436. value = field.clean(['', '2013-04-09'])
  437. self.assertEqual(value, DateRange(None, datetime.date(2013, 4, 9)))
  438. def test_date_incorrect_data_type(self):
  439. field = pg_forms.DateRangeField()
  440. with self.assertRaises(exceptions.ValidationError) as cm:
  441. field.clean('1')
  442. self.assertEqual(cm.exception.messages[0], 'Enter two valid dates.')
  443. self.assertEqual(cm.exception.code, 'invalid')
  444. def test_date_invalid_lower(self):
  445. field = pg_forms.DateRangeField()
  446. with self.assertRaises(exceptions.ValidationError) as cm:
  447. field.clean(['a', '2013-04-09'])
  448. self.assertEqual(cm.exception.messages[0], 'Enter a valid date.')
  449. def test_date_invalid_upper(self):
  450. field = pg_forms.DateRangeField()
  451. with self.assertRaises(exceptions.ValidationError) as cm:
  452. field.clean(['2013-04-09', 'b'])
  453. self.assertEqual(cm.exception.messages[0], 'Enter a valid date.')
  454. def test_date_required(self):
  455. field = pg_forms.DateRangeField(required=True)
  456. with self.assertRaises(exceptions.ValidationError) as cm:
  457. field.clean(['', ''])
  458. self.assertEqual(cm.exception.messages[0], 'This field is required.')
  459. value = field.clean(['1976-04-16', ''])
  460. self.assertEqual(value, DateRange(datetime.date(1976, 4, 16), None))
  461. def test_datetime_lower_bound_higher(self):
  462. field = pg_forms.DateTimeRangeField()
  463. with self.assertRaises(exceptions.ValidationError) as cm:
  464. field.clean(['2006-10-25 14:59', '2006-10-25 14:58'])
  465. self.assertEqual(cm.exception.messages[0], 'The start of the range must not exceed the end of the range.')
  466. self.assertEqual(cm.exception.code, 'bound_ordering')
  467. def test_datetime_open(self):
  468. field = pg_forms.DateTimeRangeField()
  469. value = field.clean(['', '2013-04-09 11:45'])
  470. self.assertEqual(value, DateTimeTZRange(None, datetime.datetime(2013, 4, 9, 11, 45)))
  471. def test_datetime_incorrect_data_type(self):
  472. field = pg_forms.DateTimeRangeField()
  473. with self.assertRaises(exceptions.ValidationError) as cm:
  474. field.clean('2013-04-09 11:45')
  475. self.assertEqual(cm.exception.messages[0], 'Enter two valid date/times.')
  476. self.assertEqual(cm.exception.code, 'invalid')
  477. def test_datetime_invalid_lower(self):
  478. field = pg_forms.DateTimeRangeField()
  479. with self.assertRaises(exceptions.ValidationError) as cm:
  480. field.clean(['45', '2013-04-09 11:45'])
  481. self.assertEqual(cm.exception.messages[0], 'Enter a valid date/time.')
  482. def test_datetime_invalid_upper(self):
  483. field = pg_forms.DateTimeRangeField()
  484. with self.assertRaises(exceptions.ValidationError) as cm:
  485. field.clean(['2013-04-09 11:45', 'sweet pickles'])
  486. self.assertEqual(cm.exception.messages[0], 'Enter a valid date/time.')
  487. def test_datetime_required(self):
  488. field = pg_forms.DateTimeRangeField(required=True)
  489. with self.assertRaises(exceptions.ValidationError) as cm:
  490. field.clean(['', ''])
  491. self.assertEqual(cm.exception.messages[0], 'This field is required.')
  492. value = field.clean(['2013-04-09 11:45', ''])
  493. self.assertEqual(value, DateTimeTZRange(datetime.datetime(2013, 4, 9, 11, 45), None))
  494. @override_settings(USE_TZ=True, TIME_ZONE='Africa/Johannesburg')
  495. def test_datetime_prepare_value(self):
  496. field = pg_forms.DateTimeRangeField()
  497. value = field.prepare_value(
  498. DateTimeTZRange(datetime.datetime(2015, 5, 22, 16, 6, 33, tzinfo=timezone.utc), None)
  499. )
  500. self.assertEqual(value, [datetime.datetime(2015, 5, 22, 18, 6, 33), None])
  501. def test_model_field_formfield_integer(self):
  502. model_field = pg_fields.IntegerRangeField()
  503. form_field = model_field.formfield()
  504. self.assertIsInstance(form_field, pg_forms.IntegerRangeField)
  505. def test_model_field_formfield_biginteger(self):
  506. model_field = pg_fields.BigIntegerRangeField()
  507. form_field = model_field.formfield()
  508. self.assertIsInstance(form_field, pg_forms.IntegerRangeField)
  509. def test_model_field_formfield_float(self):
  510. model_field = pg_fields.FloatRangeField()
  511. form_field = model_field.formfield()
  512. self.assertIsInstance(form_field, pg_forms.FloatRangeField)
  513. def test_model_field_formfield_date(self):
  514. model_field = pg_fields.DateRangeField()
  515. form_field = model_field.formfield()
  516. self.assertIsInstance(form_field, pg_forms.DateRangeField)
  517. def test_model_field_formfield_datetime(self):
  518. model_field = pg_fields.DateTimeRangeField()
  519. form_field = model_field.formfield()
  520. self.assertIsInstance(form_field, pg_forms.DateTimeRangeField)
  521. class TestWidget(PostgreSQLTestCase):
  522. def test_range_widget(self):
  523. f = pg_forms.ranges.DateTimeRangeField()
  524. self.assertHTMLEqual(
  525. f.widget.render('datetimerange', ''),
  526. '<input type="text" name="datetimerange_0" /><input type="text" name="datetimerange_1" />'
  527. )
  528. self.assertHTMLEqual(
  529. f.widget.render('datetimerange', None),
  530. '<input type="text" name="datetimerange_0" /><input type="text" name="datetimerange_1" />'
  531. )
  532. dt_range = DateTimeTZRange(
  533. datetime.datetime(2006, 1, 10, 7, 30),
  534. datetime.datetime(2006, 2, 12, 9, 50)
  535. )
  536. self.assertHTMLEqual(
  537. f.widget.render('datetimerange', dt_range),
  538. '<input type="text" name="datetimerange_0" value="2006-01-10 07:30:00" />'
  539. '<input type="text" name="datetimerange_1" value="2006-02-12 09:50:00" />'
  540. )