ranges.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. import warnings
  2. from psycopg2.extras import DateRange, DateTimeTZRange, NumericRange
  3. from django import forms
  4. from django.core import exceptions
  5. from django.forms.widgets import HiddenInput, MultiWidget
  6. from django.utils.deprecation import RemovedInDjango31Warning
  7. from django.utils.translation import gettext_lazy as _
  8. __all__ = [
  9. 'BaseRangeField', 'IntegerRangeField', 'DecimalRangeField',
  10. 'DateTimeRangeField', 'DateRangeField', 'FloatRangeField',
  11. 'HiddenRangeWidget', 'RangeWidget',
  12. ]
  13. class RangeWidget(MultiWidget):
  14. def __init__(self, base_widget, attrs=None):
  15. widgets = (base_widget, base_widget)
  16. super().__init__(widgets, attrs)
  17. def decompress(self, value):
  18. if value:
  19. return (value.lower, value.upper)
  20. return (None, None)
  21. class HiddenRangeWidget(RangeWidget):
  22. """A widget that splits input into two <input type="hidden"> inputs."""
  23. def __init__(self, attrs=None):
  24. super().__init__(HiddenInput, attrs)
  25. class BaseRangeField(forms.MultiValueField):
  26. default_error_messages = {
  27. 'invalid': _('Enter two valid values.'),
  28. 'bound_ordering': _('The start of the range must not exceed the end of the range.'),
  29. }
  30. hidden_widget = HiddenRangeWidget
  31. def __init__(self, **kwargs):
  32. if 'widget' not in kwargs:
  33. kwargs['widget'] = RangeWidget(self.base_field.widget)
  34. if 'fields' not in kwargs:
  35. kwargs['fields'] = [self.base_field(required=False), self.base_field(required=False)]
  36. kwargs.setdefault('required', False)
  37. kwargs.setdefault('require_all_fields', False)
  38. super().__init__(**kwargs)
  39. def prepare_value(self, value):
  40. lower_base, upper_base = self.fields
  41. if isinstance(value, self.range_type):
  42. return [
  43. lower_base.prepare_value(value.lower),
  44. upper_base.prepare_value(value.upper),
  45. ]
  46. if value is None:
  47. return [
  48. lower_base.prepare_value(None),
  49. upper_base.prepare_value(None),
  50. ]
  51. return value
  52. def compress(self, values):
  53. if not values:
  54. return None
  55. lower, upper = values
  56. if lower is not None and upper is not None and lower > upper:
  57. raise exceptions.ValidationError(
  58. self.error_messages['bound_ordering'],
  59. code='bound_ordering',
  60. )
  61. try:
  62. range_value = self.range_type(lower, upper)
  63. except TypeError:
  64. raise exceptions.ValidationError(
  65. self.error_messages['invalid'],
  66. code='invalid',
  67. )
  68. else:
  69. return range_value
  70. class IntegerRangeField(BaseRangeField):
  71. default_error_messages = {'invalid': _('Enter two whole numbers.')}
  72. base_field = forms.IntegerField
  73. range_type = NumericRange
  74. class DecimalRangeField(BaseRangeField):
  75. default_error_messages = {'invalid': _('Enter two numbers.')}
  76. base_field = forms.DecimalField
  77. range_type = NumericRange
  78. class FloatRangeField(DecimalRangeField):
  79. base_field = forms.FloatField
  80. def __init__(self, **kwargs):
  81. warnings.warn(
  82. 'FloatRangeField is deprecated in favor of DecimalRangeField.',
  83. RemovedInDjango31Warning, stacklevel=2,
  84. )
  85. super().__init__(**kwargs)
  86. class DateTimeRangeField(BaseRangeField):
  87. default_error_messages = {'invalid': _('Enter two valid date/times.')}
  88. base_field = forms.DateTimeField
  89. range_type = DateTimeTZRange
  90. class DateRangeField(BaseRangeField):
  91. default_error_messages = {'invalid': _('Enter two valid dates.')}
  92. base_field = forms.DateField
  93. range_type = DateRange