- Author:
- evenicoulddoit
- Posted:
- April 24, 2015
- Language:
- Python
- Version:
- 1.5
- Score:
- 0 (after 0 ratings)
I wanted to be able to restyle the inputs, which required having access to each of the select widgets. When used in a form, you can simply iterate over the field to access each element.
Example: {% for form_field in form.date %} <div class="select-wrap"> {{ form_field }} </div> {% endfor %}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 | import datetime
import re
from django.conf import settings
from django.forms.extras import SelectDateWidget
from django.forms.widgets import Widget, Select
from django.utils import datetime_safe, six
from django.utils.dates import MONTHS
from django.utils.formats import get_format
from django.utils.safestring import mark_safe
RE_DATE = re.compile(
r'(?P<year>[1-9]\d{3})-(?P<month>[1-9]\d?)-(?P<day>[1-9]\d?)'
)
def _parse_date_fmt():
"""
Parse the date format in the settings for a year, month, and day.
"""
fmt = get_format('DATE_FORMAT')
escaped = False
for char in fmt:
if escaped:
escaped = False
elif char == '\\':
escaped = True
elif char in 'Yy':
yield 'year'
elif char in 'bEFMmNn':
yield 'month'
elif char in 'dj':
yield 'day'
class IterableSelectDateWidget(Widget, object):
"""
A widget for selecting dates with sub-widgets for the year/month/day.
"""
none_value = (0, '---')
month_field = '{0}_month'
day_field = '{0}_day'
year_field = '{0}_year'
def __init__(self, attrs=None, years=None, required=True):
"""
Initialise the widget and sub-widgets.
"""
self.attrs = attrs or {}
self.required = required
if years:
self.years = years
else:
this_year = datetime.date.today().year
self.years = range(this_year, this_year+10)
self._subwidgets = [
self.create_individual(component)
for component in _parse_date_fmt()
]
def create_individual(self, component_name):
"""
Create an individual widget.
"""
id_template = getattr(self, "{0}_field".format(component_name))
choices = []
if not self.required:
choices.insert(0, self.none_value)
if component_name == "day":
choices.extend([(i, i) for i in range(1, 32)])
elif component_name == "month":
choices.extend(list(six.iteritems(MONTHS)))
elif component_name == "year":
choices.extend([(i, i) for i in self.years])
return SelectDateSubWidget(
component_name, choices, id_template, self.attrs
)
def render(self, name, value, attrs=None):
"""
Render the combined widget.
"""
return mark_safe("\n".join((
widget.render(name, value, attrs) for widget in self._subwidgets
)))
def subwidgets(self, name, value, attrs=None):
"""
Return a generator for the rendered sub-widgets.
"""
for subwidget in self._subwidgets:
yield subwidget.render(name, value, attrs)
def id_for_label(self, id_):
"""
Return the first sub-widget's ID.
"""
try:
return '{0}_{1}'.format(id_, self._subwidgets[0].component)
except IndexError:
return '{0}_month'.format(id_)
def value_from_datadict(self, data, files, name):
"""
Parse the given data for the individual values, and combine them.
"""
y = data.get(self.year_field.format(name))
m = data.get(self.month_field.format(name))
d = data.get(self.day_field.format(name))
if y == m == d == "0":
return None
if y and m and d:
if settings.USE_L10N:
input_format = get_format('DATE_INPUT_FORMATS')[0]
try:
date_value = datetime.date(int(y), int(m), int(d))
except ValueError:
return '{0}-{1}-{2}'.format(y, m, d)
else:
date_value = datetime_safe.new_date(date_value)
return date_value.strftime(input_format)
else:
return '{0}-{1}-{2}'.format(y, m, d)
return data.get(name, None)
def _has_changed(self, initial, data):
"""
Attempt to parse the given date to determine whether its changed.
"""
try:
input_format = get_format('DATE_INPUT_FORMATS')[0]
data = datetime_safe.datetime.strptime(data, input_format).date()
except (TypeError, ValueError):
pass
return super(IterableSelectDateWidget, self)._has_changed(
initial, data
)
class SelectDateSubWidget(Select, object):
"""
An individual component (year/month/day) for the select date widget.
"""
def __init__(self, component, choices, id_template, attrs):
super(SelectDateSubWidget, self).__init__(choices=choices, attrs=attrs)
self.attrs = attrs
self.component = component
self.id_template = id_template
def render(self, name, value, attrs=None):
"""
Render the individual select widget.
"""
value = self._parse_value(value)
id_ = attrs['id'] if "id" in self.attrs else "id_{0}".format(name)
local_attrs = self.build_attrs(id=self.id_template.format(id_))
return super(SelectDateSubWidget, self).render(
self.id_template.format(name), value, local_attrs
)
def _parse_value(self, value):
"""
Get the sub-value for the given component (day/month/year).
"""
try:
return getattr(value, self.component)
except AttributeError:
if isinstance(value, six.string_types):
if settings.USE_L10N:
try:
input_format = get_format('DATE_INPUT_FORMATS')[0]
v = datetime.datetime.strptime(value, input_format)
return getattr(v, self.component)
except ValueError:
pass
else:
match = RE_DATE.match(value)
if match:
return match.groupdict()[self.component]
|
More like this
- Template tag - list punctuation for a list of items by shapiromatron 10 months, 1 week ago
- JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 2 weeks ago
- Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
- Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
- Help text hyperlinks by sa2812 1 year, 6 months ago
Comments
Please login first before commenting.