1. import datetime
    
  2. import json
    
  3. from decimal import Decimal
    
  4. 
    
  5. from django import forms
    
  6. from django.core import exceptions, serializers
    
  7. from django.db.models import DateField, DateTimeField, F, Func, Value
    
  8. from django.http import QueryDict
    
  9. from django.test import override_settings
    
  10. from django.test.utils import isolate_apps
    
  11. from django.utils import timezone
    
  12. 
    
  13. from . import PostgreSQLSimpleTestCase, PostgreSQLTestCase
    
  14. from .models import (
    
  15.     BigAutoFieldModel,
    
  16.     PostgreSQLModel,
    
  17.     RangeLookupsModel,
    
  18.     RangesModel,
    
  19.     SmallAutoFieldModel,
    
  20. )
    
  21. 
    
  22. try:
    
  23.     from psycopg2.extras import DateRange, DateTimeTZRange, NumericRange
    
  24. 
    
  25.     from django.contrib.postgres import fields as pg_fields
    
  26.     from django.contrib.postgres import forms as pg_forms
    
  27.     from django.contrib.postgres.validators import (
    
  28.         RangeMaxValueValidator,
    
  29.         RangeMinValueValidator,
    
  30.     )
    
  31. except ImportError:
    
  32.     pass
    
  33. 
    
  34. 
    
  35. @isolate_apps("postgres_tests")
    
  36. class BasicTests(PostgreSQLSimpleTestCase):
    
  37.     def test_get_field_display(self):
    
  38.         class Model(PostgreSQLModel):
    
  39.             field = pg_fields.IntegerRangeField(
    
  40.                 choices=[
    
  41.                     ["1-50", [((1, 25), "1-25"), ([26, 50], "26-50")]],
    
  42.                     ((51, 100), "51-100"),
    
  43.                 ],
    
  44.             )
    
  45. 
    
  46.         tests = (
    
  47.             ((1, 25), "1-25"),
    
  48.             ([26, 50], "26-50"),
    
  49.             ((51, 100), "51-100"),
    
  50.             ((1, 2), "(1, 2)"),
    
  51.             ([1, 2], "[1, 2]"),
    
  52.         )
    
  53.         for value, display in tests:
    
  54.             with self.subTest(value=value, display=display):
    
  55.                 instance = Model(field=value)
    
  56.                 self.assertEqual(instance.get_field_display(), display)
    
  57. 
    
  58.     def test_discrete_range_fields_unsupported_default_bounds(self):
    
  59.         discrete_range_types = [
    
  60.             pg_fields.BigIntegerRangeField,
    
  61.             pg_fields.IntegerRangeField,
    
  62.             pg_fields.DateRangeField,
    
  63.         ]
    
  64.         for field_type in discrete_range_types:
    
  65.             msg = f"Cannot use 'default_bounds' with {field_type.__name__}."
    
  66.             with self.assertRaisesMessage(TypeError, msg):
    
  67.                 field_type(choices=[((51, 100), "51-100")], default_bounds="[]")
    
  68. 
    
  69.     def test_continuous_range_fields_default_bounds(self):
    
  70.         continuous_range_types = [
    
  71.             pg_fields.DecimalRangeField,
    
  72.             pg_fields.DateTimeRangeField,
    
  73.         ]
    
  74.         for field_type in continuous_range_types:
    
  75.             field = field_type(choices=[((51, 100), "51-100")], default_bounds="[]")
    
  76.             self.assertEqual(field.default_bounds, "[]")
    
  77. 
    
  78.     def test_invalid_default_bounds(self):
    
  79.         tests = [")]", ")[", "](", "])", "([", "[(", "x", "", None]
    
  80.         msg = "default_bounds must be one of '[)', '(]', '()', or '[]'."
    
  81.         for invalid_bounds in tests:
    
  82.             with self.assertRaisesMessage(ValueError, msg):
    
  83.                 pg_fields.DecimalRangeField(default_bounds=invalid_bounds)
    
  84. 
    
  85.     def test_deconstruct(self):
    
  86.         field = pg_fields.DecimalRangeField()
    
  87.         *_, kwargs = field.deconstruct()
    
  88.         self.assertEqual(kwargs, {})
    
  89.         field = pg_fields.DecimalRangeField(default_bounds="[]")
    
  90.         *_, kwargs = field.deconstruct()
    
  91.         self.assertEqual(kwargs, {"default_bounds": "[]"})
    
  92. 
    
  93. 
    
  94. class TestSaveLoad(PostgreSQLTestCase):
    
  95.     def test_all_fields(self):
    
  96.         now = timezone.now()
    
  97.         instance = RangesModel(
    
  98.             ints=NumericRange(0, 10),
    
  99.             bigints=NumericRange(10, 20),
    
  100.             decimals=NumericRange(20, 30),
    
  101.             timestamps=DateTimeTZRange(now - datetime.timedelta(hours=1), now),
    
  102.             dates=DateRange(now.date() - datetime.timedelta(days=1), now.date()),
    
  103.         )
    
  104.         instance.save()
    
  105.         loaded = RangesModel.objects.get()
    
  106.         self.assertEqual(instance.ints, loaded.ints)
    
  107.         self.assertEqual(instance.bigints, loaded.bigints)
    
  108.         self.assertEqual(instance.decimals, loaded.decimals)
    
  109.         self.assertEqual(instance.timestamps, loaded.timestamps)
    
  110.         self.assertEqual(instance.dates, loaded.dates)
    
  111. 
    
  112.     def test_range_object(self):
    
  113.         r = NumericRange(0, 10)
    
  114.         instance = RangesModel(ints=r)
    
  115.         instance.save()
    
  116.         loaded = RangesModel.objects.get()
    
  117.         self.assertEqual(r, loaded.ints)
    
  118. 
    
  119.     def test_tuple(self):
    
  120.         instance = RangesModel(ints=(0, 10))
    
  121.         instance.save()
    
  122.         loaded = RangesModel.objects.get()
    
  123.         self.assertEqual(NumericRange(0, 10), loaded.ints)
    
  124. 
    
  125.     def test_tuple_range_with_default_bounds(self):
    
  126.         range_ = (timezone.now(), timezone.now() + datetime.timedelta(hours=1))
    
  127.         RangesModel.objects.create(timestamps_closed_bounds=range_, timestamps=range_)
    
  128.         loaded = RangesModel.objects.get()
    
  129.         self.assertEqual(
    
  130.             loaded.timestamps_closed_bounds,
    
  131.             DateTimeTZRange(range_[0], range_[1], "[]"),
    
  132.         )
    
  133.         self.assertEqual(
    
  134.             loaded.timestamps,
    
  135.             DateTimeTZRange(range_[0], range_[1], "[)"),
    
  136.         )
    
  137. 
    
  138.     def test_range_object_boundaries(self):
    
  139.         r = NumericRange(0, 10, "[]")
    
  140.         instance = RangesModel(decimals=r)
    
  141.         instance.save()
    
  142.         loaded = RangesModel.objects.get()
    
  143.         self.assertEqual(r, loaded.decimals)
    
  144.         self.assertIn(10, loaded.decimals)
    
  145. 
    
  146.     def test_range_object_boundaries_range_with_default_bounds(self):
    
  147.         range_ = DateTimeTZRange(
    
  148.             timezone.now(),
    
  149.             timezone.now() + datetime.timedelta(hours=1),
    
  150.             bounds="()",
    
  151.         )
    
  152.         RangesModel.objects.create(timestamps_closed_bounds=range_)
    
  153.         loaded = RangesModel.objects.get()
    
  154.         self.assertEqual(loaded.timestamps_closed_bounds, range_)
    
  155. 
    
  156.     def test_unbounded(self):
    
  157.         r = NumericRange(None, None, "()")
    
  158.         instance = RangesModel(decimals=r)
    
  159.         instance.save()
    
  160.         loaded = RangesModel.objects.get()
    
  161.         self.assertEqual(r, loaded.decimals)
    
  162. 
    
  163.     def test_empty(self):
    
  164.         r = NumericRange(empty=True)
    
  165.         instance = RangesModel(ints=r)
    
  166.         instance.save()
    
  167.         loaded = RangesModel.objects.get()
    
  168.         self.assertEqual(r, loaded.ints)
    
  169. 
    
  170.     def test_null(self):
    
  171.         instance = RangesModel(ints=None)
    
  172.         instance.save()
    
  173.         loaded = RangesModel.objects.get()
    
  174.         self.assertIsNone(loaded.ints)
    
  175. 
    
  176.     def test_model_set_on_base_field(self):
    
  177.         instance = RangesModel()
    
  178.         field = instance._meta.get_field("ints")
    
  179.         self.assertEqual(field.model, RangesModel)
    
  180.         self.assertEqual(field.base_field.model, RangesModel)
    
  181. 
    
  182. 
    
  183. class TestRangeContainsLookup(PostgreSQLTestCase):
    
  184.     @classmethod
    
  185.     def setUpTestData(cls):
    
  186.         cls.timestamps = [
    
  187.             datetime.datetime(year=2016, month=1, day=1),
    
  188.             datetime.datetime(year=2016, month=1, day=2, hour=1),
    
  189.             datetime.datetime(year=2016, month=1, day=2, hour=12),
    
  190.             datetime.datetime(year=2016, month=1, day=3),
    
  191.             datetime.datetime(year=2016, month=1, day=3, hour=1),
    
  192.             datetime.datetime(year=2016, month=2, day=2),
    
  193.         ]
    
  194.         cls.aware_timestamps = [
    
  195.             timezone.make_aware(timestamp) for timestamp in cls.timestamps
    
  196.         ]
    
  197.         cls.dates = [
    
  198.             datetime.date(year=2016, month=1, day=1),
    
  199.             datetime.date(year=2016, month=1, day=2),
    
  200.             datetime.date(year=2016, month=1, day=3),
    
  201.             datetime.date(year=2016, month=1, day=4),
    
  202.             datetime.date(year=2016, month=2, day=2),
    
  203.             datetime.date(year=2016, month=2, day=3),
    
  204.         ]
    
  205.         cls.obj = RangesModel.objects.create(
    
  206.             dates=(cls.dates[0], cls.dates[3]),
    
  207.             dates_inner=(cls.dates[1], cls.dates[2]),
    
  208.             timestamps=(cls.timestamps[0], cls.timestamps[3]),
    
  209.             timestamps_inner=(cls.timestamps[1], cls.timestamps[2]),
    
  210.         )
    
  211.         cls.aware_obj = RangesModel.objects.create(
    
  212.             dates=(cls.dates[0], cls.dates[3]),
    
  213.             dates_inner=(cls.dates[1], cls.dates[2]),
    
  214.             timestamps=(cls.aware_timestamps[0], cls.aware_timestamps[3]),
    
  215.             timestamps_inner=(cls.timestamps[1], cls.timestamps[2]),
    
  216.         )
    
  217.         # Objects that don't match any queries.
    
  218.         for i in range(3, 4):
    
  219.             RangesModel.objects.create(
    
  220.                 dates=(cls.dates[i], cls.dates[i + 1]),
    
  221.                 timestamps=(cls.timestamps[i], cls.timestamps[i + 1]),
    
  222.             )
    
  223.             RangesModel.objects.create(
    
  224.                 dates=(cls.dates[i], cls.dates[i + 1]),
    
  225.                 timestamps=(cls.aware_timestamps[i], cls.aware_timestamps[i + 1]),
    
  226.             )
    
  227. 
    
  228.     def test_datetime_range_contains(self):
    
  229.         filter_args = (
    
  230.             self.timestamps[1],
    
  231.             self.aware_timestamps[1],
    
  232.             (self.timestamps[1], self.timestamps[2]),
    
  233.             (self.aware_timestamps[1], self.aware_timestamps[2]),
    
  234.             Value(self.dates[0]),
    
  235.             Func(F("dates"), function="lower", output_field=DateTimeField()),
    
  236.             F("timestamps_inner"),
    
  237.         )
    
  238.         for filter_arg in filter_args:
    
  239.             with self.subTest(filter_arg=filter_arg):
    
  240.                 self.assertCountEqual(
    
  241.                     RangesModel.objects.filter(**{"timestamps__contains": filter_arg}),
    
  242.                     [self.obj, self.aware_obj],
    
  243.                 )
    
  244. 
    
  245.     def test_date_range_contains(self):
    
  246.         filter_args = (
    
  247.             self.timestamps[1],
    
  248.             (self.dates[1], self.dates[2]),
    
  249.             Value(self.dates[0], output_field=DateField()),
    
  250.             Func(F("timestamps"), function="lower", output_field=DateField()),
    
  251.             F("dates_inner"),
    
  252.         )
    
  253.         for filter_arg in filter_args:
    
  254.             with self.subTest(filter_arg=filter_arg):
    
  255.                 self.assertCountEqual(
    
  256.                     RangesModel.objects.filter(**{"dates__contains": filter_arg}),
    
  257.                     [self.obj, self.aware_obj],
    
  258.                 )
    
  259. 
    
  260. 
    
  261. class TestQuerying(PostgreSQLTestCase):
    
  262.     @classmethod
    
  263.     def setUpTestData(cls):
    
  264.         cls.objs = RangesModel.objects.bulk_create(
    
  265.             [
    
  266.                 RangesModel(ints=NumericRange(0, 10)),
    
  267.                 RangesModel(ints=NumericRange(5, 15)),
    
  268.                 RangesModel(ints=NumericRange(None, 0)),
    
  269.                 RangesModel(ints=NumericRange(empty=True)),
    
  270.                 RangesModel(ints=None),
    
  271.             ]
    
  272.         )
    
  273. 
    
  274.     def test_exact(self):
    
  275.         self.assertSequenceEqual(
    
  276.             RangesModel.objects.filter(ints__exact=NumericRange(0, 10)),
    
  277.             [self.objs[0]],
    
  278.         )
    
  279. 
    
  280.     def test_isnull(self):
    
  281.         self.assertSequenceEqual(
    
  282.             RangesModel.objects.filter(ints__isnull=True),
    
  283.             [self.objs[4]],
    
  284.         )
    
  285. 
    
  286.     def test_isempty(self):
    
  287.         self.assertSequenceEqual(
    
  288.             RangesModel.objects.filter(ints__isempty=True),
    
  289.             [self.objs[3]],
    
  290.         )
    
  291. 
    
  292.     def test_contains(self):
    
  293.         self.assertSequenceEqual(
    
  294.             RangesModel.objects.filter(ints__contains=8),
    
  295.             [self.objs[0], self.objs[1]],
    
  296.         )
    
  297. 
    
  298.     def test_contains_range(self):
    
  299.         self.assertSequenceEqual(
    
  300.             RangesModel.objects.filter(ints__contains=NumericRange(3, 8)),
    
  301.             [self.objs[0]],
    
  302.         )
    
  303. 
    
  304.     def test_contained_by(self):
    
  305.         self.assertSequenceEqual(
    
  306.             RangesModel.objects.filter(ints__contained_by=NumericRange(0, 20)),
    
  307.             [self.objs[0], self.objs[1], self.objs[3]],
    
  308.         )
    
  309. 
    
  310.     def test_overlap(self):
    
  311.         self.assertSequenceEqual(
    
  312.             RangesModel.objects.filter(ints__overlap=NumericRange(3, 8)),
    
  313.             [self.objs[0], self.objs[1]],
    
  314.         )
    
  315. 
    
  316.     def test_fully_lt(self):
    
  317.         self.assertSequenceEqual(
    
  318.             RangesModel.objects.filter(ints__fully_lt=NumericRange(5, 10)),
    
  319.             [self.objs[2]],
    
  320.         )
    
  321. 
    
  322.     def test_fully_gt(self):
    
  323.         self.assertSequenceEqual(
    
  324.             RangesModel.objects.filter(ints__fully_gt=NumericRange(5, 10)),
    
  325.             [],
    
  326.         )
    
  327. 
    
  328.     def test_not_lt(self):
    
  329.         self.assertSequenceEqual(
    
  330.             RangesModel.objects.filter(ints__not_lt=NumericRange(5, 10)),
    
  331.             [self.objs[1]],
    
  332.         )
    
  333. 
    
  334.     def test_not_gt(self):
    
  335.         self.assertSequenceEqual(
    
  336.             RangesModel.objects.filter(ints__not_gt=NumericRange(5, 10)),
    
  337.             [self.objs[0], self.objs[2]],
    
  338.         )
    
  339. 
    
  340.     def test_adjacent_to(self):
    
  341.         self.assertSequenceEqual(
    
  342.             RangesModel.objects.filter(ints__adjacent_to=NumericRange(0, 5)),
    
  343.             [self.objs[1], self.objs[2]],
    
  344.         )
    
  345. 
    
  346.     def test_startswith(self):
    
  347.         self.assertSequenceEqual(
    
  348.             RangesModel.objects.filter(ints__startswith=0),
    
  349.             [self.objs[0]],
    
  350.         )
    
  351. 
    
  352.     def test_endswith(self):
    
  353.         self.assertSequenceEqual(
    
  354.             RangesModel.objects.filter(ints__endswith=0),
    
  355.             [self.objs[2]],
    
  356.         )
    
  357. 
    
  358.     def test_startswith_chaining(self):
    
  359.         self.assertSequenceEqual(
    
  360.             RangesModel.objects.filter(ints__startswith__gte=0),
    
  361.             [self.objs[0], self.objs[1]],
    
  362.         )
    
  363. 
    
  364.     def test_bound_type(self):
    
  365.         decimals = RangesModel.objects.bulk_create(
    
  366.             [
    
  367.                 RangesModel(decimals=NumericRange(None, 10)),
    
  368.                 RangesModel(decimals=NumericRange(10, None)),
    
  369.                 RangesModel(decimals=NumericRange(5, 15)),
    
  370.                 RangesModel(decimals=NumericRange(5, 15, "(]")),
    
  371.             ]
    
  372.         )
    
  373.         tests = [
    
  374.             ("lower_inc", True, [decimals[1], decimals[2]]),
    
  375.             ("lower_inc", False, [decimals[0], decimals[3]]),
    
  376.             ("lower_inf", True, [decimals[0]]),
    
  377.             ("lower_inf", False, [decimals[1], decimals[2], decimals[3]]),
    
  378.             ("upper_inc", True, [decimals[3]]),
    
  379.             ("upper_inc", False, [decimals[0], decimals[1], decimals[2]]),
    
  380.             ("upper_inf", True, [decimals[1]]),
    
  381.             ("upper_inf", False, [decimals[0], decimals[2], decimals[3]]),
    
  382.         ]
    
  383.         for lookup, filter_arg, excepted_result in tests:
    
  384.             with self.subTest(lookup=lookup, filter_arg=filter_arg):
    
  385.                 self.assertSequenceEqual(
    
  386.                     RangesModel.objects.filter(**{"decimals__%s" % lookup: filter_arg}),
    
  387.                     excepted_result,
    
  388.                 )
    
  389. 
    
  390. 
    
  391. class TestQueryingWithRanges(PostgreSQLTestCase):
    
  392.     def test_date_range(self):
    
  393.         objs = [
    
  394.             RangeLookupsModel.objects.create(date="2015-01-01"),
    
  395.             RangeLookupsModel.objects.create(date="2015-05-05"),
    
  396.         ]
    
  397.         self.assertSequenceEqual(
    
  398.             RangeLookupsModel.objects.filter(
    
  399.                 date__contained_by=DateRange("2015-01-01", "2015-05-04")
    
  400.             ),
    
  401.             [objs[0]],
    
  402.         )
    
  403. 
    
  404.     def test_date_range_datetime_field(self):
    
  405.         objs = [
    
  406.             RangeLookupsModel.objects.create(timestamp="2015-01-01"),
    
  407.             RangeLookupsModel.objects.create(timestamp="2015-05-05"),
    
  408.         ]
    
  409.         self.assertSequenceEqual(
    
  410.             RangeLookupsModel.objects.filter(
    
  411.                 timestamp__date__contained_by=DateRange("2015-01-01", "2015-05-04")
    
  412.             ),
    
  413.             [objs[0]],
    
  414.         )
    
  415. 
    
  416.     def test_datetime_range(self):
    
  417.         objs = [
    
  418.             RangeLookupsModel.objects.create(timestamp="2015-01-01T09:00:00"),
    
  419.             RangeLookupsModel.objects.create(timestamp="2015-05-05T17:00:00"),
    
  420.         ]
    
  421.         self.assertSequenceEqual(
    
  422.             RangeLookupsModel.objects.filter(
    
  423.                 timestamp__contained_by=DateTimeTZRange(
    
  424.                     "2015-01-01T09:00", "2015-05-04T23:55"
    
  425.                 )
    
  426.             ),
    
  427.             [objs[0]],
    
  428.         )
    
  429. 
    
  430.     def test_small_integer_field_contained_by(self):
    
  431.         objs = [
    
  432.             RangeLookupsModel.objects.create(small_integer=8),
    
  433.             RangeLookupsModel.objects.create(small_integer=4),
    
  434.             RangeLookupsModel.objects.create(small_integer=-1),
    
  435.         ]
    
  436.         self.assertSequenceEqual(
    
  437.             RangeLookupsModel.objects.filter(
    
  438.                 small_integer__contained_by=NumericRange(4, 6)
    
  439.             ),
    
  440.             [objs[1]],
    
  441.         )
    
  442. 
    
  443.     def test_integer_range(self):
    
  444.         objs = [
    
  445.             RangeLookupsModel.objects.create(integer=5),
    
  446.             RangeLookupsModel.objects.create(integer=99),
    
  447.             RangeLookupsModel.objects.create(integer=-1),
    
  448.         ]
    
  449.         self.assertSequenceEqual(
    
  450.             RangeLookupsModel.objects.filter(integer__contained_by=NumericRange(1, 98)),
    
  451.             [objs[0]],
    
  452.         )
    
  453. 
    
  454.     def test_biginteger_range(self):
    
  455.         objs = [
    
  456.             RangeLookupsModel.objects.create(big_integer=5),
    
  457.             RangeLookupsModel.objects.create(big_integer=99),
    
  458.             RangeLookupsModel.objects.create(big_integer=-1),
    
  459.         ]
    
  460.         self.assertSequenceEqual(
    
  461.             RangeLookupsModel.objects.filter(
    
  462.                 big_integer__contained_by=NumericRange(1, 98)
    
  463.             ),
    
  464.             [objs[0]],
    
  465.         )
    
  466. 
    
  467.     def test_decimal_field_contained_by(self):
    
  468.         objs = [
    
  469.             RangeLookupsModel.objects.create(decimal_field=Decimal("1.33")),
    
  470.             RangeLookupsModel.objects.create(decimal_field=Decimal("2.88")),
    
  471.             RangeLookupsModel.objects.create(decimal_field=Decimal("99.17")),
    
  472.         ]
    
  473.         self.assertSequenceEqual(
    
  474.             RangeLookupsModel.objects.filter(
    
  475.                 decimal_field__contained_by=NumericRange(
    
  476.                     Decimal("1.89"), Decimal("7.91")
    
  477.                 ),
    
  478.             ),
    
  479.             [objs[1]],
    
  480.         )
    
  481. 
    
  482.     def test_float_range(self):
    
  483.         objs = [
    
  484.             RangeLookupsModel.objects.create(float=5),
    
  485.             RangeLookupsModel.objects.create(float=99),
    
  486.             RangeLookupsModel.objects.create(float=-1),
    
  487.         ]
    
  488.         self.assertSequenceEqual(
    
  489.             RangeLookupsModel.objects.filter(float__contained_by=NumericRange(1, 98)),
    
  490.             [objs[0]],
    
  491.         )
    
  492. 
    
  493.     def test_small_auto_field_contained_by(self):
    
  494.         objs = SmallAutoFieldModel.objects.bulk_create(
    
  495.             [SmallAutoFieldModel() for i in range(1, 5)]
    
  496.         )
    
  497.         self.assertSequenceEqual(
    
  498.             SmallAutoFieldModel.objects.filter(
    
  499.                 id__contained_by=NumericRange(objs[1].pk, objs[3].pk),
    
  500.             ),
    
  501.             objs[1:3],
    
  502.         )
    
  503. 
    
  504.     def test_auto_field_contained_by(self):
    
  505.         objs = RangeLookupsModel.objects.bulk_create(
    
  506.             [RangeLookupsModel() for i in range(1, 5)]
    
  507.         )
    
  508.         self.assertSequenceEqual(
    
  509.             RangeLookupsModel.objects.filter(
    
  510.                 id__contained_by=NumericRange(objs[1].pk, objs[3].pk),
    
  511.             ),
    
  512.             objs[1:3],
    
  513.         )
    
  514. 
    
  515.     def test_big_auto_field_contained_by(self):
    
  516.         objs = BigAutoFieldModel.objects.bulk_create(
    
  517.             [BigAutoFieldModel() for i in range(1, 5)]
    
  518.         )
    
  519.         self.assertSequenceEqual(
    
  520.             BigAutoFieldModel.objects.filter(
    
  521.                 id__contained_by=NumericRange(objs[1].pk, objs[3].pk),
    
  522.             ),
    
  523.             objs[1:3],
    
  524.         )
    
  525. 
    
  526.     def test_f_ranges(self):
    
  527.         parent = RangesModel.objects.create(decimals=NumericRange(0, 10))
    
  528.         objs = [
    
  529.             RangeLookupsModel.objects.create(float=5, parent=parent),
    
  530.             RangeLookupsModel.objects.create(float=99, parent=parent),
    
  531.         ]
    
  532.         self.assertSequenceEqual(
    
  533.             RangeLookupsModel.objects.filter(float__contained_by=F("parent__decimals")),
    
  534.             [objs[0]],
    
  535.         )
    
  536. 
    
  537.     def test_exclude(self):
    
  538.         objs = [
    
  539.             RangeLookupsModel.objects.create(float=5),
    
  540.             RangeLookupsModel.objects.create(float=99),
    
  541.             RangeLookupsModel.objects.create(float=-1),
    
  542.         ]
    
  543.         self.assertSequenceEqual(
    
  544.             RangeLookupsModel.objects.exclude(float__contained_by=NumericRange(0, 100)),
    
  545.             [objs[2]],
    
  546.         )
    
  547. 
    
  548. 
    
  549. class TestSerialization(PostgreSQLSimpleTestCase):
    
  550.     test_data = (
    
  551.         '[{"fields": {"ints": "{\\"upper\\": \\"10\\", \\"lower\\": \\"0\\", '
    
  552.         '\\"bounds\\": \\"[)\\"}", "decimals": "{\\"empty\\": true}", '
    
  553.         '"bigints": null, "timestamps": '
    
  554.         '"{\\"upper\\": \\"2014-02-02T12:12:12+00:00\\", '
    
  555.         '\\"lower\\": \\"2014-01-01T00:00:00+00:00\\", \\"bounds\\": \\"[)\\"}", '
    
  556.         '"timestamps_inner": null, '
    
  557.         '"timestamps_closed_bounds": "{\\"upper\\": \\"2014-02-02T12:12:12+00:00\\", '
    
  558.         '\\"lower\\": \\"2014-01-01T00:00:00+00:00\\", \\"bounds\\": \\"()\\"}", '
    
  559.         '"dates": "{\\"upper\\": \\"2014-02-02\\", \\"lower\\": \\"2014-01-01\\", '
    
  560.         '\\"bounds\\": \\"[)\\"}", "dates_inner": null }, '
    
  561.         '"model": "postgres_tests.rangesmodel", "pk": null}]'
    
  562.     )
    
  563. 
    
  564.     lower_date = datetime.date(2014, 1, 1)
    
  565.     upper_date = datetime.date(2014, 2, 2)
    
  566.     lower_dt = datetime.datetime(2014, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc)
    
  567.     upper_dt = datetime.datetime(2014, 2, 2, 12, 12, 12, tzinfo=datetime.timezone.utc)
    
  568. 
    
  569.     def test_dumping(self):
    
  570.         instance = RangesModel(
    
  571.             ints=NumericRange(0, 10),
    
  572.             decimals=NumericRange(empty=True),
    
  573.             timestamps=DateTimeTZRange(self.lower_dt, self.upper_dt),
    
  574.             timestamps_closed_bounds=DateTimeTZRange(
    
  575.                 self.lower_dt,
    
  576.                 self.upper_dt,
    
  577.                 bounds="()",
    
  578.             ),
    
  579.             dates=DateRange(self.lower_date, self.upper_date),
    
  580.         )
    
  581.         data = serializers.serialize("json", [instance])
    
  582.         dumped = json.loads(data)
    
  583.         for field in ("ints", "dates", "timestamps", "timestamps_closed_bounds"):
    
  584.             dumped[0]["fields"][field] = json.loads(dumped[0]["fields"][field])
    
  585.         check = json.loads(self.test_data)
    
  586.         for field in ("ints", "dates", "timestamps", "timestamps_closed_bounds"):
    
  587.             check[0]["fields"][field] = json.loads(check[0]["fields"][field])
    
  588. 
    
  589.         self.assertEqual(dumped, check)
    
  590. 
    
  591.     def test_loading(self):
    
  592.         instance = list(serializers.deserialize("json", self.test_data))[0].object
    
  593.         self.assertEqual(instance.ints, NumericRange(0, 10))
    
  594.         self.assertEqual(instance.decimals, NumericRange(empty=True))
    
  595.         self.assertIsNone(instance.bigints)
    
  596.         self.assertEqual(instance.dates, DateRange(self.lower_date, self.upper_date))
    
  597.         self.assertEqual(
    
  598.             instance.timestamps, DateTimeTZRange(self.lower_dt, self.upper_dt)
    
  599.         )
    
  600.         self.assertEqual(
    
  601.             instance.timestamps_closed_bounds,
    
  602.             DateTimeTZRange(self.lower_dt, self.upper_dt, bounds="()"),
    
  603.         )
    
  604. 
    
  605.     def test_serialize_range_with_null(self):
    
  606.         instance = RangesModel(ints=NumericRange(None, 10))
    
  607.         data = serializers.serialize("json", [instance])
    
  608.         new_instance = list(serializers.deserialize("json", data))[0].object
    
  609.         self.assertEqual(new_instance.ints, NumericRange(None, 10))
    
  610. 
    
  611.         instance = RangesModel(ints=NumericRange(10, None))
    
  612.         data = serializers.serialize("json", [instance])
    
  613.         new_instance = list(serializers.deserialize("json", data))[0].object
    
  614.         self.assertEqual(new_instance.ints, NumericRange(10, None))
    
  615. 
    
  616. 
    
  617. class TestChecks(PostgreSQLSimpleTestCase):
    
  618.     def test_choices_tuple_list(self):
    
  619.         class Model(PostgreSQLModel):
    
  620.             field = pg_fields.IntegerRangeField(
    
  621.                 choices=[
    
  622.                     ["1-50", [((1, 25), "1-25"), ([26, 50], "26-50")]],
    
  623.                     ((51, 100), "51-100"),
    
  624.                 ],
    
  625.             )
    
  626. 
    
  627.         self.assertEqual(Model._meta.get_field("field").check(), [])
    
  628. 
    
  629. 
    
  630. class TestValidators(PostgreSQLSimpleTestCase):
    
  631.     def test_max(self):
    
  632.         validator = RangeMaxValueValidator(5)
    
  633.         validator(NumericRange(0, 5))
    
  634.         msg = "Ensure that this range is completely less than or equal to 5."
    
  635.         with self.assertRaises(exceptions.ValidationError) as cm:
    
  636.             validator(NumericRange(0, 10))
    
  637.         self.assertEqual(cm.exception.messages[0], msg)
    
  638.         self.assertEqual(cm.exception.code, "max_value")
    
  639.         with self.assertRaisesMessage(exceptions.ValidationError, msg):
    
  640.             validator(NumericRange(0, None))  # an unbound range
    
  641. 
    
  642.     def test_min(self):
    
  643.         validator = RangeMinValueValidator(5)
    
  644.         validator(NumericRange(10, 15))
    
  645.         msg = "Ensure that this range is completely greater than or equal to 5."
    
  646.         with self.assertRaises(exceptions.ValidationError) as cm:
    
  647.             validator(NumericRange(0, 10))
    
  648.         self.assertEqual(cm.exception.messages[0], msg)
    
  649.         self.assertEqual(cm.exception.code, "min_value")
    
  650.         with self.assertRaisesMessage(exceptions.ValidationError, msg):
    
  651.             validator(NumericRange(None, 10))  # an unbound range
    
  652. 
    
  653. 
    
  654. class TestFormField(PostgreSQLSimpleTestCase):
    
  655.     def test_valid_integer(self):
    
  656.         field = pg_forms.IntegerRangeField()
    
  657.         value = field.clean(["1", "2"])
    
  658.         self.assertEqual(value, NumericRange(1, 2))
    
  659. 
    
  660.     def test_valid_decimal(self):
    
  661.         field = pg_forms.DecimalRangeField()
    
  662.         value = field.clean(["1.12345", "2.001"])
    
  663.         self.assertEqual(value, NumericRange(Decimal("1.12345"), Decimal("2.001")))
    
  664. 
    
  665.     def test_valid_timestamps(self):
    
  666.         field = pg_forms.DateTimeRangeField()
    
  667.         value = field.clean(["01/01/2014 00:00:00", "02/02/2014 12:12:12"])
    
  668.         lower = datetime.datetime(2014, 1, 1, 0, 0, 0)
    
  669.         upper = datetime.datetime(2014, 2, 2, 12, 12, 12)
    
  670.         self.assertEqual(value, DateTimeTZRange(lower, upper))
    
  671. 
    
  672.     def test_valid_dates(self):
    
  673.         field = pg_forms.DateRangeField()
    
  674.         value = field.clean(["01/01/2014", "02/02/2014"])
    
  675.         lower = datetime.date(2014, 1, 1)
    
  676.         upper = datetime.date(2014, 2, 2)
    
  677.         self.assertEqual(value, DateRange(lower, upper))
    
  678. 
    
  679.     def test_using_split_datetime_widget(self):
    
  680.         class SplitDateTimeRangeField(pg_forms.DateTimeRangeField):
    
  681.             base_field = forms.SplitDateTimeField
    
  682. 
    
  683.         class SplitForm(forms.Form):
    
  684.             field = SplitDateTimeRangeField()
    
  685. 
    
  686.         form = SplitForm()
    
  687.         self.assertHTMLEqual(
    
  688.             str(form),
    
  689.             """
    
  690.             <div>
    
  691.                 <fieldset>
    
  692.                     <legend>Field:</legend>
    
  693.                     <input id="id_field_0_0" name="field_0_0" type="text">
    
  694.                     <input id="id_field_0_1" name="field_0_1" type="text">
    
  695.                     <input id="id_field_1_0" name="field_1_0" type="text">
    
  696.                     <input id="id_field_1_1" name="field_1_1" type="text">
    
  697.                 </fieldset>
    
  698.             </div>
    
  699.         """,
    
  700.         )
    
  701.         form = SplitForm(
    
  702.             {
    
  703.                 "field_0_0": "01/01/2014",
    
  704.                 "field_0_1": "00:00:00",
    
  705.                 "field_1_0": "02/02/2014",
    
  706.                 "field_1_1": "12:12:12",
    
  707.             }
    
  708.         )
    
  709.         self.assertTrue(form.is_valid())
    
  710.         lower = datetime.datetime(2014, 1, 1, 0, 0, 0)
    
  711.         upper = datetime.datetime(2014, 2, 2, 12, 12, 12)
    
  712.         self.assertEqual(form.cleaned_data["field"], DateTimeTZRange(lower, upper))
    
  713. 
    
  714.     def test_none(self):
    
  715.         field = pg_forms.IntegerRangeField(required=False)
    
  716.         value = field.clean(["", ""])
    
  717.         self.assertIsNone(value)
    
  718. 
    
  719.     def test_datetime_form_as_table(self):
    
  720.         class DateTimeRangeForm(forms.Form):
    
  721.             datetime_field = pg_forms.DateTimeRangeField(show_hidden_initial=True)
    
  722. 
    
  723.         form = DateTimeRangeForm()
    
  724.         self.assertHTMLEqual(
    
  725.             form.as_table(),
    
  726.             """
    
  727.             <tr><th>
    
  728.             <label>Datetime field:</label>
    
  729.             </th><td>
    
  730.             <input type="text" name="datetime_field_0" id="id_datetime_field_0">
    
  731.             <input type="text" name="datetime_field_1" id="id_datetime_field_1">
    
  732.             <input type="hidden" name="initial-datetime_field_0"
    
  733.             id="initial-id_datetime_field_0">
    
  734.             <input type="hidden" name="initial-datetime_field_1"
    
  735.             id="initial-id_datetime_field_1">
    
  736.             </td></tr>
    
  737.             """,
    
  738.         )
    
  739.         form = DateTimeRangeForm(
    
  740.             {
    
  741.                 "datetime_field_0": "2010-01-01 11:13:00",
    
  742.                 "datetime_field_1": "2020-12-12 16:59:00",
    
  743.             }
    
  744.         )
    
  745.         self.assertHTMLEqual(
    
  746.             form.as_table(),
    
  747.             """
    
  748.             <tr><th>
    
  749.             <label>Datetime field:</label>
    
  750.             </th><td>
    
  751.             <input type="text" name="datetime_field_0"
    
  752.             value="2010-01-01 11:13:00" id="id_datetime_field_0">
    
  753.             <input type="text" name="datetime_field_1"
    
  754.             value="2020-12-12 16:59:00" id="id_datetime_field_1">
    
  755.             <input type="hidden" name="initial-datetime_field_0"
    
  756.             value="2010-01-01 11:13:00" id="initial-id_datetime_field_0">
    
  757.             <input type="hidden" name="initial-datetime_field_1"
    
  758.             value="2020-12-12 16:59:00" id="initial-id_datetime_field_1"></td></tr>
    
  759.             """,
    
  760.         )
    
  761. 
    
  762.     def test_datetime_form_initial_data(self):
    
  763.         class DateTimeRangeForm(forms.Form):
    
  764.             datetime_field = pg_forms.DateTimeRangeField(show_hidden_initial=True)
    
  765. 
    
  766.         data = QueryDict(mutable=True)
    
  767.         data.update(
    
  768.             {
    
  769.                 "datetime_field_0": "2010-01-01 11:13:00",
    
  770.                 "datetime_field_1": "",
    
  771.                 "initial-datetime_field_0": "2010-01-01 10:12:00",
    
  772.                 "initial-datetime_field_1": "",
    
  773.             }
    
  774.         )
    
  775.         form = DateTimeRangeForm(data=data)
    
  776.         self.assertTrue(form.has_changed())
    
  777. 
    
  778.         data["initial-datetime_field_0"] = "2010-01-01 11:13:00"
    
  779.         form = DateTimeRangeForm(data=data)
    
  780.         self.assertFalse(form.has_changed())
    
  781. 
    
  782.     def test_rendering(self):
    
  783.         class RangeForm(forms.Form):
    
  784.             ints = pg_forms.IntegerRangeField()
    
  785. 
    
  786.         self.assertHTMLEqual(
    
  787.             str(RangeForm()),
    
  788.             """
    
  789.         <div>
    
  790.             <fieldset>
    
  791.                 <legend>Ints:</legend>
    
  792.                 <input id="id_ints_0" name="ints_0" type="number">
    
  793.                 <input id="id_ints_1" name="ints_1" type="number">
    
  794.             </fieldset>
    
  795.         </div>
    
  796.         """,
    
  797.         )
    
  798. 
    
  799.     def test_integer_lower_bound_higher(self):
    
  800.         field = pg_forms.IntegerRangeField()
    
  801.         with self.assertRaises(exceptions.ValidationError) as cm:
    
  802.             field.clean(["10", "2"])
    
  803.         self.assertEqual(
    
  804.             cm.exception.messages[0],
    
  805.             "The start of the range must not exceed the end of the range.",
    
  806.         )
    
  807.         self.assertEqual(cm.exception.code, "bound_ordering")
    
  808. 
    
  809.     def test_integer_open(self):
    
  810.         field = pg_forms.IntegerRangeField()
    
  811.         value = field.clean(["", "0"])
    
  812.         self.assertEqual(value, NumericRange(None, 0))
    
  813. 
    
  814.     def test_integer_incorrect_data_type(self):
    
  815.         field = pg_forms.IntegerRangeField()
    
  816.         with self.assertRaises(exceptions.ValidationError) as cm:
    
  817.             field.clean("1")
    
  818.         self.assertEqual(cm.exception.messages[0], "Enter two whole numbers.")
    
  819.         self.assertEqual(cm.exception.code, "invalid")
    
  820. 
    
  821.     def test_integer_invalid_lower(self):
    
  822.         field = pg_forms.IntegerRangeField()
    
  823.         with self.assertRaises(exceptions.ValidationError) as cm:
    
  824.             field.clean(["a", "2"])
    
  825.         self.assertEqual(cm.exception.messages[0], "Enter a whole number.")
    
  826. 
    
  827.     def test_integer_invalid_upper(self):
    
  828.         field = pg_forms.IntegerRangeField()
    
  829.         with self.assertRaises(exceptions.ValidationError) as cm:
    
  830.             field.clean(["1", "b"])
    
  831.         self.assertEqual(cm.exception.messages[0], "Enter a whole number.")
    
  832. 
    
  833.     def test_integer_required(self):
    
  834.         field = pg_forms.IntegerRangeField(required=True)
    
  835.         with self.assertRaises(exceptions.ValidationError) as cm:
    
  836.             field.clean(["", ""])
    
  837.         self.assertEqual(cm.exception.messages[0], "This field is required.")
    
  838.         value = field.clean([1, ""])
    
  839.         self.assertEqual(value, NumericRange(1, None))
    
  840. 
    
  841.     def test_decimal_lower_bound_higher(self):
    
  842.         field = pg_forms.DecimalRangeField()
    
  843.         with self.assertRaises(exceptions.ValidationError) as cm:
    
  844.             field.clean(["1.8", "1.6"])
    
  845.         self.assertEqual(
    
  846.             cm.exception.messages[0],
    
  847.             "The start of the range must not exceed the end of the range.",
    
  848.         )
    
  849.         self.assertEqual(cm.exception.code, "bound_ordering")
    
  850. 
    
  851.     def test_decimal_open(self):
    
  852.         field = pg_forms.DecimalRangeField()
    
  853.         value = field.clean(["", "3.1415926"])
    
  854.         self.assertEqual(value, NumericRange(None, Decimal("3.1415926")))
    
  855. 
    
  856.     def test_decimal_incorrect_data_type(self):
    
  857.         field = pg_forms.DecimalRangeField()
    
  858.         with self.assertRaises(exceptions.ValidationError) as cm:
    
  859.             field.clean("1.6")
    
  860.         self.assertEqual(cm.exception.messages[0], "Enter two numbers.")
    
  861.         self.assertEqual(cm.exception.code, "invalid")
    
  862. 
    
  863.     def test_decimal_invalid_lower(self):
    
  864.         field = pg_forms.DecimalRangeField()
    
  865.         with self.assertRaises(exceptions.ValidationError) as cm:
    
  866.             field.clean(["a", "3.1415926"])
    
  867.         self.assertEqual(cm.exception.messages[0], "Enter a number.")
    
  868. 
    
  869.     def test_decimal_invalid_upper(self):
    
  870.         field = pg_forms.DecimalRangeField()
    
  871.         with self.assertRaises(exceptions.ValidationError) as cm:
    
  872.             field.clean(["1.61803399", "b"])
    
  873.         self.assertEqual(cm.exception.messages[0], "Enter a number.")
    
  874. 
    
  875.     def test_decimal_required(self):
    
  876.         field = pg_forms.DecimalRangeField(required=True)
    
  877.         with self.assertRaises(exceptions.ValidationError) as cm:
    
  878.             field.clean(["", ""])
    
  879.         self.assertEqual(cm.exception.messages[0], "This field is required.")
    
  880.         value = field.clean(["1.61803399", ""])
    
  881.         self.assertEqual(value, NumericRange(Decimal("1.61803399"), None))
    
  882. 
    
  883.     def test_date_lower_bound_higher(self):
    
  884.         field = pg_forms.DateRangeField()
    
  885.         with self.assertRaises(exceptions.ValidationError) as cm:
    
  886.             field.clean(["2013-04-09", "1976-04-16"])
    
  887.         self.assertEqual(
    
  888.             cm.exception.messages[0],
    
  889.             "The start of the range must not exceed the end of the range.",
    
  890.         )
    
  891.         self.assertEqual(cm.exception.code, "bound_ordering")
    
  892. 
    
  893.     def test_date_open(self):
    
  894.         field = pg_forms.DateRangeField()
    
  895.         value = field.clean(["", "2013-04-09"])
    
  896.         self.assertEqual(value, DateRange(None, datetime.date(2013, 4, 9)))
    
  897. 
    
  898.     def test_date_incorrect_data_type(self):
    
  899.         field = pg_forms.DateRangeField()
    
  900.         with self.assertRaises(exceptions.ValidationError) as cm:
    
  901.             field.clean("1")
    
  902.         self.assertEqual(cm.exception.messages[0], "Enter two valid dates.")
    
  903.         self.assertEqual(cm.exception.code, "invalid")
    
  904. 
    
  905.     def test_date_invalid_lower(self):
    
  906.         field = pg_forms.DateRangeField()
    
  907.         with self.assertRaises(exceptions.ValidationError) as cm:
    
  908.             field.clean(["a", "2013-04-09"])
    
  909.         self.assertEqual(cm.exception.messages[0], "Enter a valid date.")
    
  910. 
    
  911.     def test_date_invalid_upper(self):
    
  912.         field = pg_forms.DateRangeField()
    
  913.         with self.assertRaises(exceptions.ValidationError) as cm:
    
  914.             field.clean(["2013-04-09", "b"])
    
  915.         self.assertEqual(cm.exception.messages[0], "Enter a valid date.")
    
  916. 
    
  917.     def test_date_required(self):
    
  918.         field = pg_forms.DateRangeField(required=True)
    
  919.         with self.assertRaises(exceptions.ValidationError) as cm:
    
  920.             field.clean(["", ""])
    
  921.         self.assertEqual(cm.exception.messages[0], "This field is required.")
    
  922.         value = field.clean(["1976-04-16", ""])
    
  923.         self.assertEqual(value, DateRange(datetime.date(1976, 4, 16), None))
    
  924. 
    
  925.     def test_date_has_changed_first(self):
    
  926.         self.assertTrue(
    
  927.             pg_forms.DateRangeField().has_changed(
    
  928.                 ["2010-01-01", "2020-12-12"],
    
  929.                 ["2010-01-31", "2020-12-12"],
    
  930.             )
    
  931.         )
    
  932. 
    
  933.     def test_date_has_changed_last(self):
    
  934.         self.assertTrue(
    
  935.             pg_forms.DateRangeField().has_changed(
    
  936.                 ["2010-01-01", "2020-12-12"],
    
  937.                 ["2010-01-01", "2020-12-31"],
    
  938.             )
    
  939.         )
    
  940. 
    
  941.     def test_datetime_lower_bound_higher(self):
    
  942.         field = pg_forms.DateTimeRangeField()
    
  943.         with self.assertRaises(exceptions.ValidationError) as cm:
    
  944.             field.clean(["2006-10-25 14:59", "2006-10-25 14:58"])
    
  945.         self.assertEqual(
    
  946.             cm.exception.messages[0],
    
  947.             "The start of the range must not exceed the end of the range.",
    
  948.         )
    
  949.         self.assertEqual(cm.exception.code, "bound_ordering")
    
  950. 
    
  951.     def test_datetime_open(self):
    
  952.         field = pg_forms.DateTimeRangeField()
    
  953.         value = field.clean(["", "2013-04-09 11:45"])
    
  954.         self.assertEqual(
    
  955.             value, DateTimeTZRange(None, datetime.datetime(2013, 4, 9, 11, 45))
    
  956.         )
    
  957. 
    
  958.     def test_datetime_incorrect_data_type(self):
    
  959.         field = pg_forms.DateTimeRangeField()
    
  960.         with self.assertRaises(exceptions.ValidationError) as cm:
    
  961.             field.clean("2013-04-09 11:45")
    
  962.         self.assertEqual(cm.exception.messages[0], "Enter two valid date/times.")
    
  963.         self.assertEqual(cm.exception.code, "invalid")
    
  964. 
    
  965.     def test_datetime_invalid_lower(self):
    
  966.         field = pg_forms.DateTimeRangeField()
    
  967.         with self.assertRaises(exceptions.ValidationError) as cm:
    
  968.             field.clean(["45", "2013-04-09 11:45"])
    
  969.         self.assertEqual(cm.exception.messages[0], "Enter a valid date/time.")
    
  970. 
    
  971.     def test_datetime_invalid_upper(self):
    
  972.         field = pg_forms.DateTimeRangeField()
    
  973.         with self.assertRaises(exceptions.ValidationError) as cm:
    
  974.             field.clean(["2013-04-09 11:45", "sweet pickles"])
    
  975.         self.assertEqual(cm.exception.messages[0], "Enter a valid date/time.")
    
  976. 
    
  977.     def test_datetime_required(self):
    
  978.         field = pg_forms.DateTimeRangeField(required=True)
    
  979.         with self.assertRaises(exceptions.ValidationError) as cm:
    
  980.             field.clean(["", ""])
    
  981.         self.assertEqual(cm.exception.messages[0], "This field is required.")
    
  982.         value = field.clean(["2013-04-09 11:45", ""])
    
  983.         self.assertEqual(
    
  984.             value, DateTimeTZRange(datetime.datetime(2013, 4, 9, 11, 45), None)
    
  985.         )
    
  986. 
    
  987.     @override_settings(USE_TZ=True, TIME_ZONE="Africa/Johannesburg")
    
  988.     def test_datetime_prepare_value(self):
    
  989.         field = pg_forms.DateTimeRangeField()
    
  990.         value = field.prepare_value(
    
  991.             DateTimeTZRange(
    
  992.                 datetime.datetime(2015, 5, 22, 16, 6, 33, tzinfo=datetime.timezone.utc),
    
  993.                 None,
    
  994.             )
    
  995.         )
    
  996.         self.assertEqual(value, [datetime.datetime(2015, 5, 22, 18, 6, 33), None])
    
  997. 
    
  998.     def test_datetime_has_changed_first(self):
    
  999.         self.assertTrue(
    
  1000.             pg_forms.DateTimeRangeField().has_changed(
    
  1001.                 ["2010-01-01 00:00", "2020-12-12 00:00"],
    
  1002.                 ["2010-01-31 23:00", "2020-12-12 00:00"],
    
  1003.             )
    
  1004.         )
    
  1005. 
    
  1006.     def test_datetime_has_changed_last(self):
    
  1007.         self.assertTrue(
    
  1008.             pg_forms.DateTimeRangeField().has_changed(
    
  1009.                 ["2010-01-01 00:00", "2020-12-12 00:00"],
    
  1010.                 ["2010-01-01 00:00", "2020-12-31 23:00"],
    
  1011.             )
    
  1012.         )
    
  1013. 
    
  1014.     def test_model_field_formfield_integer(self):
    
  1015.         model_field = pg_fields.IntegerRangeField()
    
  1016.         form_field = model_field.formfield()
    
  1017.         self.assertIsInstance(form_field, pg_forms.IntegerRangeField)
    
  1018.         self.assertEqual(form_field.range_kwargs, {})
    
  1019. 
    
  1020.     def test_model_field_formfield_biginteger(self):
    
  1021.         model_field = pg_fields.BigIntegerRangeField()
    
  1022.         form_field = model_field.formfield()
    
  1023.         self.assertIsInstance(form_field, pg_forms.IntegerRangeField)
    
  1024.         self.assertEqual(form_field.range_kwargs, {})
    
  1025. 
    
  1026.     def test_model_field_formfield_float(self):
    
  1027.         model_field = pg_fields.DecimalRangeField(default_bounds="()")
    
  1028.         form_field = model_field.formfield()
    
  1029.         self.assertIsInstance(form_field, pg_forms.DecimalRangeField)
    
  1030.         self.assertEqual(form_field.range_kwargs, {"bounds": "()"})
    
  1031. 
    
  1032.     def test_model_field_formfield_date(self):
    
  1033.         model_field = pg_fields.DateRangeField()
    
  1034.         form_field = model_field.formfield()
    
  1035.         self.assertIsInstance(form_field, pg_forms.DateRangeField)
    
  1036.         self.assertEqual(form_field.range_kwargs, {})
    
  1037. 
    
  1038.     def test_model_field_formfield_datetime(self):
    
  1039.         model_field = pg_fields.DateTimeRangeField()
    
  1040.         form_field = model_field.formfield()
    
  1041.         self.assertIsInstance(form_field, pg_forms.DateTimeRangeField)
    
  1042.         self.assertEqual(
    
  1043.             form_field.range_kwargs,
    
  1044.             {"bounds": pg_fields.ranges.CANONICAL_RANGE_BOUNDS},
    
  1045.         )
    
  1046. 
    
  1047.     def test_model_field_formfield_datetime_default_bounds(self):
    
  1048.         model_field = pg_fields.DateTimeRangeField(default_bounds="[]")
    
  1049.         form_field = model_field.formfield()
    
  1050.         self.assertIsInstance(form_field, pg_forms.DateTimeRangeField)
    
  1051.         self.assertEqual(form_field.range_kwargs, {"bounds": "[]"})
    
  1052. 
    
  1053.     def test_model_field_with_default_bounds(self):
    
  1054.         field = pg_forms.DateTimeRangeField(default_bounds="[]")
    
  1055.         value = field.clean(["2014-01-01 00:00:00", "2014-02-03 12:13:14"])
    
  1056.         lower = datetime.datetime(2014, 1, 1, 0, 0, 0)
    
  1057.         upper = datetime.datetime(2014, 2, 3, 12, 13, 14)
    
  1058.         self.assertEqual(value, DateTimeTZRange(lower, upper, "[]"))
    
  1059. 
    
  1060.     def test_has_changed(self):
    
  1061.         for field, value in (
    
  1062.             (pg_forms.DateRangeField(), ["2010-01-01", "2020-12-12"]),
    
  1063.             (pg_forms.DateTimeRangeField(), ["2010-01-01 11:13", "2020-12-12 14:52"]),
    
  1064.             (pg_forms.IntegerRangeField(), [1, 2]),
    
  1065.             (pg_forms.DecimalRangeField(), ["1.12345", "2.001"]),
    
  1066.         ):
    
  1067.             with self.subTest(field=field.__class__.__name__):
    
  1068.                 self.assertTrue(field.has_changed(None, value))
    
  1069.                 self.assertTrue(field.has_changed([value[0], ""], value))
    
  1070.                 self.assertTrue(field.has_changed(["", value[1]], value))
    
  1071.                 self.assertFalse(field.has_changed(value, value))
    
  1072. 
    
  1073. 
    
  1074. class TestWidget(PostgreSQLSimpleTestCase):
    
  1075.     def test_range_widget(self):
    
  1076.         f = pg_forms.ranges.DateTimeRangeField()
    
  1077.         self.assertHTMLEqual(
    
  1078.             f.widget.render("datetimerange", ""),
    
  1079.             '<input type="text" name="datetimerange_0">'
    
  1080.             '<input type="text" name="datetimerange_1">',
    
  1081.         )
    
  1082.         self.assertHTMLEqual(
    
  1083.             f.widget.render("datetimerange", None),
    
  1084.             '<input type="text" name="datetimerange_0">'
    
  1085.             '<input type="text" name="datetimerange_1">',
    
  1086.         )
    
  1087.         dt_range = DateTimeTZRange(
    
  1088.             datetime.datetime(2006, 1, 10, 7, 30), datetime.datetime(2006, 2, 12, 9, 50)
    
  1089.         )
    
  1090.         self.assertHTMLEqual(
    
  1091.             f.widget.render("datetimerange", dt_range),
    
  1092.             '<input type="text" name="datetimerange_0" value="2006-01-10 07:30:00">'
    
  1093.             '<input type="text" name="datetimerange_1" value="2006-02-12 09:50:00">',
    
  1094.         )
    
  1095. 
    
  1096.     def test_range_widget_render_tuple_value(self):
    
  1097.         field = pg_forms.ranges.DateTimeRangeField()
    
  1098.         dt_range_tuple = (
    
  1099.             datetime.datetime(2022, 4, 22, 10, 24),
    
  1100.             datetime.datetime(2022, 5, 12, 9, 25),
    
  1101.         )
    
  1102.         self.assertHTMLEqual(
    
  1103.             field.widget.render("datetimerange", dt_range_tuple),
    
  1104.             '<input type="text" name="datetimerange_0" value="2022-04-22 10:24:00">'
    
  1105.             '<input type="text" name="datetimerange_1" value="2022-05-12 09:25:00">',
    
  1106.         )