You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ranges.py 3.4KB

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