test_ranges.py 25 KB

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