2016-02-13 15:31:28 +01:00
|
|
|
# Bootstrap django-datetime-widget is a simple and clean widget for DateField,
|
|
|
|
# Timefiled and DateTimeField in Django framework. It is based on Bootstrap
|
2016-06-19 13:15:05 +02:00
|
|
|
# datetime picker, supports Bootstrap 2
|
2016-02-13 15:31:28 +01:00
|
|
|
#
|
|
|
|
# https://github.com/asaglimbeni/django-datetime-widget
|
|
|
|
#
|
|
|
|
# License: BSD
|
2016-06-18 12:47:25 +02:00
|
|
|
# Initial Author: Alfredo Saglimbeni
|
2016-02-13 15:31:28 +01:00
|
|
|
|
2016-06-18 12:47:25 +02:00
|
|
|
import json
|
2016-02-13 15:31:28 +01:00
|
|
|
import re
|
|
|
|
import uuid
|
|
|
|
|
2018-11-13 21:31:28 +01:00
|
|
|
from django.forms.widgets import DateTimeInput, TimeInput, SelectMultiple
|
2016-06-26 23:56:19 +02:00
|
|
|
from django.utils.formats import get_language
|
2016-02-13 15:31:28 +01:00
|
|
|
from django.utils.safestring import mark_safe
|
|
|
|
|
2016-06-18 12:47:25 +02:00
|
|
|
DATE_FORMAT_JS_PY_MAPPING = {
|
2016-02-13 15:31:28 +01:00
|
|
|
'P': '%p',
|
|
|
|
'ss': '%S',
|
|
|
|
'ii': '%M',
|
|
|
|
'hh': '%H',
|
|
|
|
'HH': '%I',
|
|
|
|
'dd': '%d',
|
|
|
|
'mm': '%m',
|
|
|
|
'yy': '%y',
|
|
|
|
'yyyy': '%Y',
|
|
|
|
}
|
|
|
|
|
2016-06-18 12:47:25 +02:00
|
|
|
DATE_FORMAT_TO_PYTHON_REGEX = re.compile(r'\b(' + '|'.join(DATE_FORMAT_JS_PY_MAPPING.keys()) + r')\b')
|
2016-02-13 15:31:28 +01:00
|
|
|
|
|
|
|
|
2016-06-18 12:47:25 +02:00
|
|
|
DATE_FORMAT_PY_JS_MAPPING = {
|
2016-02-13 15:31:28 +01:00
|
|
|
'%M': 'ii',
|
|
|
|
'%m': 'mm',
|
|
|
|
'%I': 'HH',
|
|
|
|
'%H': 'hh',
|
|
|
|
'%d': 'dd',
|
|
|
|
'%Y': 'yyyy',
|
|
|
|
'%y': 'yy',
|
|
|
|
'%p': 'P',
|
|
|
|
'%S': 'ss'
|
|
|
|
}
|
|
|
|
|
2016-06-18 12:47:25 +02:00
|
|
|
DATE_FORMAT_TO_JS_REGEX = re.compile(r'(?<!\w)(' + '|'.join(DATE_FORMAT_PY_JS_MAPPING.keys()) + r')\b')
|
2016-02-13 15:31:28 +01:00
|
|
|
|
|
|
|
|
2016-06-19 13:15:05 +02:00
|
|
|
BOOTSTRAP_INPUT_TEMPLATE = """
|
2016-02-13 15:31:28 +01:00
|
|
|
<div id="%(id)s" class="controls input-append date">
|
|
|
|
%(rendered_widget)s
|
|
|
|
%(clear_button)s
|
|
|
|
<span class="add-on"><i class="icon-th"></i></span>
|
|
|
|
</div>
|
|
|
|
<script type="text/javascript">
|
|
|
|
$("#%(id)s").datetimepicker({%(options)s});
|
|
|
|
</script>
|
|
|
|
"""
|
|
|
|
|
2016-06-19 13:15:05 +02:00
|
|
|
CLEAR_BTN_TEMPLATE = """<span class="add-on"><i class="icon-remove"></i></span>"""
|
2016-02-13 15:31:28 +01:00
|
|
|
|
|
|
|
|
|
|
|
class PickerWidgetMixin(object):
|
|
|
|
|
|
|
|
format_name = None
|
|
|
|
glyphicon = None
|
|
|
|
|
2016-06-19 13:15:05 +02:00
|
|
|
def __init__(self, attrs=None, options=None, usel10n=None):
|
2016-02-13 15:31:28 +01:00
|
|
|
|
|
|
|
if attrs is None:
|
|
|
|
attrs = {'readonly': ''}
|
|
|
|
|
|
|
|
self.options = options
|
2018-01-18 11:21:07 +01:00
|
|
|
if get_language():
|
|
|
|
self.options['language'] = get_language().split('-')[0]
|
2016-06-26 23:56:19 +02:00
|
|
|
|
|
|
|
# We're not doing localisation, get the Javascript date format provided by the user,
|
|
|
|
# with a default, and convert it to a Python data format for later string parsing
|
|
|
|
date_format = self.options['format']
|
|
|
|
self.format = DATE_FORMAT_TO_PYTHON_REGEX.sub(
|
|
|
|
lambda x: DATE_FORMAT_JS_PY_MAPPING[x.group()],
|
|
|
|
date_format
|
|
|
|
)
|
2016-02-13 15:31:28 +01:00
|
|
|
|
|
|
|
super(PickerWidgetMixin, self).__init__(attrs, format=self.format)
|
|
|
|
|
|
|
|
def render(self, name, value, attrs=None):
|
|
|
|
final_attrs = self.build_attrs(attrs)
|
|
|
|
rendered_widget = super(PickerWidgetMixin, self).render(name, value, final_attrs)
|
|
|
|
|
|
|
|
#if not set, autoclose have to be true.
|
|
|
|
self.options.setdefault('autoclose', True)
|
|
|
|
|
|
|
|
# Build javascript options out of python dictionary
|
|
|
|
options_list = []
|
|
|
|
for key, value in iter(self.options.items()):
|
2016-07-22 14:27:08 +02:00
|
|
|
options_list.append("%s: %s" % (key, json.dumps(value)))
|
2016-02-13 15:31:28 +01:00
|
|
|
|
|
|
|
js_options = ",\n".join(options_list)
|
|
|
|
|
|
|
|
# Use provided id or generate hex to avoid collisions in document
|
|
|
|
id = final_attrs.get('id', uuid.uuid4().hex)
|
|
|
|
|
2016-06-19 13:15:05 +02:00
|
|
|
return mark_safe(BOOTSTRAP_INPUT_TEMPLATE % dict(
|
2016-02-13 15:31:28 +01:00
|
|
|
id=id,
|
|
|
|
rendered_widget=rendered_widget,
|
2016-07-22 14:27:08 +02:00
|
|
|
clear_button=CLEAR_BTN_TEMPLATE if self.options.get('clearBtn') else '',
|
2016-02-13 15:31:28 +01:00
|
|
|
glyphicon=self.glyphicon,
|
|
|
|
options=js_options
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
class DateTimeWidget(PickerWidgetMixin, DateTimeInput):
|
|
|
|
"""
|
|
|
|
DateTimeWidget is the corresponding widget for Datetime field, it renders both the date and time
|
|
|
|
sections of the datetime picker.
|
|
|
|
"""
|
|
|
|
|
|
|
|
format_name = 'DATETIME_INPUT_FORMATS'
|
|
|
|
glyphicon = 'glyphicon-th'
|
|
|
|
|
2016-06-19 13:15:05 +02:00
|
|
|
def __init__(self, attrs=None, options=None, usel10n=None):
|
2016-02-13 15:31:28 +01:00
|
|
|
|
|
|
|
if options is None:
|
|
|
|
options = {}
|
|
|
|
|
|
|
|
# Set the default options to show only the datepicker object
|
|
|
|
options['format'] = options.get('format', 'dd/mm/yyyy hh:ii')
|
|
|
|
|
2016-06-19 13:15:05 +02:00
|
|
|
super(DateTimeWidget, self).__init__(attrs, options, usel10n)
|
2016-02-13 15:31:28 +01:00
|
|
|
|
|
|
|
|
2018-11-13 21:33:55 +01:00
|
|
|
class TimeWidget(TimeInput):
|
2016-02-13 15:31:28 +01:00
|
|
|
"""
|
2018-11-13 21:33:55 +01:00
|
|
|
TimeWidget is a widget for time selection, it uses the HTML5 "time"
|
|
|
|
input type and has a bit of a fallback mechanism with the presence
|
|
|
|
of the pattern attribute in case a standard text input is used.
|
2016-02-13 15:31:28 +01:00
|
|
|
"""
|
2018-11-13 21:33:55 +01:00
|
|
|
input_type = 'time'
|
2016-02-13 15:31:28 +01:00
|
|
|
|
2018-11-13 21:33:55 +01:00
|
|
|
def __init__(self, **kwargs):
|
|
|
|
super(TimeWidget, self).__init__(**kwargs)
|
|
|
|
self.attrs['step'] = '300' # 5 minutes
|
|
|
|
self.attrs['pattern'] = '[0-9]{2}:[0-9]{2}'
|
2018-09-22 18:12:22 +02:00
|
|
|
|
|
|
|
|
|
|
|
class WeekdaysWidget(SelectMultiple):
|
|
|
|
def render(self, name, value, attrs=None, choices=()):
|
|
|
|
s = []
|
|
|
|
value = value or []
|
|
|
|
for choice_id, choice_label in self.choices:
|
|
|
|
s.append('<li><label><input type="checkbox" '
|
|
|
|
' name="%(name)s-%(choice_id)s" %(checked)s>'
|
|
|
|
'<span>%(choice_label)s</span></label></li>' % {
|
|
|
|
'name': name,
|
|
|
|
'checked': 'checked' if choice_id in value else '',
|
|
|
|
'choice_id': choice_id,
|
|
|
|
'choice_label': choice_label})
|
|
|
|
return mark_safe('<ul id="%(id)s">' % attrs + '\n'.join(s) + '</ul>')
|
|
|
|
|
|
|
|
def value_from_datadict(self, data, files, name):
|
|
|
|
choices = []
|
|
|
|
for choice_id, choice_label in self.choices:
|
|
|
|
if data.get('%s-%s' % (name, choice_id)):
|
|
|
|
choices.append(choice_id)
|
|
|
|
return choices
|