Django’s postgres extensions support data types like DateRange
which is super useful when you want to query your database against dates, however they have no form field to expose this into HTML.
Handily Django 1.11 has made it super easy to write custom widgets with complex HTML.
Start with a form field based off MultiValueField
:
from django import forms from psycopg2.extras import DateRange class DateRangeField(forms.MultiValueField): """ A date range """ widget = DateRangeWidget def __init__(self, **kwargs): fields = ( forms.DateField(required=True), forms.DateField(required=True), ) super().__init__(fields, **kwargs) def compress(self, values): try: lower, upper = values return DateRange(lower=lower, upper=upper, bounds='[]') except ValueError: return None
The other side of a form field is a Widget
:
from django import forms from psycopg2.extras import DateRange class DateRangeWidget(forms.MultiWidget): """Date range widget.""" template_name = 'forms/widgets/daterange.html' def __init__(self, **kwargs): widgets = ( forms.DateInput(), forms.DateInput(), ) super().__init__(widgets, **kwargs) def decompress(self, value): if isinstance(value, DateRange): return (value.lower, value.upper) elif value is None: return (None, None) else: return value class Media: css = { 'all': ('//cdnjs.cloudflare.com/ajax/libs/jquery-date-range-picker/0.14.4/daterangepicker.min.css',) # noqa: E501 } js = ( '//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js', '//cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js', '//cdnjs.cloudflare.com/ajax/libs/jquery-date-range-picker/0.14.4/jquery.daterangepicker.min.js', # noqa: E501 )
Finally we can write a template to use the jquery-date-range-picker:
{% for widget in widget.subwidgets %} <input type="hidden" name="{{ widget.name }}"{% if widget.value != None %} value="{{ widget.value }}"{% endif %}{% include "django/forms/widgets/attrs.html" %} /> {% endfor %} <div id='container_for_{{ widget.attrs.id }}'></div>
With a script block:
(function() { var format = 'D/M/YYYY'; var isoFormat = 'YYYY-MM-DD'; var startInput = $('#{{ widget.subwidgets.0.attrs.id }}'); var endInput = $('#{{ widget.subwidgets.1.attrs.id }}'); $('#{{ widget.attrs.id }}').dateRangePicker({ inline: true, container: '#container_for_{{ widget.attrs.id }}', alwaysOpen: true, format: format, separator: ' ', getValue: function() { if (!startInput.val() || !endInput.val()) { return ''; } var start = moment(startInput.val(), isoFormat); var end = moment(endInput.val(), isoFormat); return start.format(format) + ' ' + end.format(format); }, setValue: function(s, start, end) { start = moment(start, format); end = moment(end, format); startInput.val(start.format(isoFormat)); endInput.val(end.format(isoFormat)); } }); })();
You can now use this DateRangeField
in a form, retrieve it from cleaned_data
for database queries or store it in a model DateRangeField
.