2006-10-29 04:34:37 +08:00
|
|
|
"""
|
|
|
|
HTML Widget classes
|
|
|
|
"""
|
2011-03-28 10:11:19 +08:00
|
|
|
|
|
|
|
import copy
|
2015-01-26 11:28:57 +08:00
|
|
|
import datetime
|
2017-07-20 23:06:30 +08:00
|
|
|
import warnings
|
2019-02-23 23:41:56 +08:00
|
|
|
from collections import defaultdict
|
2015-01-28 20:35:27 +08:00
|
|
|
from itertools import chain
|
2006-10-29 04:34:37 +08:00
|
|
|
|
2016-12-28 06:00:56 +08:00
|
|
|
from django.forms.utils import to_current_timezone
|
2015-11-07 19:24:38 +08:00
|
|
|
from django.templatetags.static import static
|
2021-05-10 21:39:50 +08:00
|
|
|
from django.utils import formats
|
2019-02-23 23:41:56 +08:00
|
|
|
from django.utils.datastructures import OrderedSet
|
2015-01-26 11:28:57 +08:00
|
|
|
from django.utils.dates import MONTHS
|
|
|
|
from django.utils.formats import get_format
|
2016-12-28 06:00:56 +08:00
|
|
|
from django.utils.html import format_html, html_safe
|
2019-10-26 22:42:32 +08:00
|
|
|
from django.utils.regex_helper import _lazy_re_compile
|
2007-11-14 20:58:53 +08:00
|
|
|
from django.utils.safestring import mark_safe
|
2019-02-23 23:41:56 +08:00
|
|
|
from django.utils.topological_sort import CyclicDependencyError, stable_topological_sort
|
2017-01-27 03:58:33 +08:00
|
|
|
from django.utils.translation import gettext_lazy as _
|
2007-05-17 05:20:35 +08:00
|
|
|
|
2016-12-28 06:00:56 +08:00
|
|
|
from .renderers import get_default_renderer
|
|
|
|
|
2007-05-17 05:20:35 +08:00
|
|
|
__all__ = (
|
2015-01-26 11:28:57 +08:00
|
|
|
"Media",
|
|
|
|
"MediaDefiningClass",
|
|
|
|
"Widget",
|
|
|
|
"TextInput",
|
|
|
|
"NumberInput",
|
|
|
|
"EmailInput",
|
|
|
|
"URLInput",
|
|
|
|
"PasswordInput",
|
|
|
|
"HiddenInput",
|
|
|
|
"MultipleHiddenInput",
|
|
|
|
"FileInput",
|
|
|
|
"ClearableFileInput",
|
|
|
|
"Textarea",
|
|
|
|
"DateInput",
|
|
|
|
"DateTimeInput",
|
|
|
|
"TimeInput",
|
|
|
|
"CheckboxInput",
|
|
|
|
"Select",
|
|
|
|
"NullBooleanSelect",
|
|
|
|
"SelectMultiple",
|
|
|
|
"RadioSelect",
|
|
|
|
"CheckboxSelectMultiple",
|
|
|
|
"MultiWidget",
|
|
|
|
"SplitDateTimeWidget",
|
|
|
|
"SplitHiddenDateTimeWidget",
|
|
|
|
"SelectDateWidget",
|
2007-05-17 05:20:35 +08:00
|
|
|
)
|
2006-11-02 09:06:12 +08:00
|
|
|
|
2013-10-27 01:50:40 +08:00
|
|
|
MEDIA_TYPES = ("css", "js")
|
2008-07-19 07:54:34 +08:00
|
|
|
|
2013-11-03 04:12:09 +08:00
|
|
|
|
2017-07-20 23:06:30 +08:00
|
|
|
class MediaOrderConflictWarning(RuntimeWarning):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2015-03-19 04:42:59 +08:00
|
|
|
@html_safe
|
2017-01-19 15:39:46 +08:00
|
|
|
class Media:
|
2017-07-20 23:06:30 +08:00
|
|
|
def __init__(self, media=None, css=None, js=None):
|
|
|
|
if media is not None:
|
|
|
|
css = getattr(media, "css", {})
|
|
|
|
js = getattr(media, "js", [])
|
2008-07-19 07:54:34 +08:00
|
|
|
else:
|
2017-07-20 23:06:30 +08:00
|
|
|
if css is None:
|
|
|
|
css = {}
|
|
|
|
if js is None:
|
|
|
|
js = []
|
2019-02-09 22:38:52 +08:00
|
|
|
self._css_lists = [css]
|
|
|
|
self._js_lists = [js]
|
2008-07-19 07:54:34 +08:00
|
|
|
|
2017-08-26 22:32:32 +08:00
|
|
|
def __repr__(self):
|
|
|
|
return "Media(css=%r, js=%r)" % (self._css, self._js)
|
|
|
|
|
2012-08-12 18:32:08 +08:00
|
|
|
def __str__(self):
|
2008-07-19 07:54:34 +08:00
|
|
|
return self.render()
|
2008-08-25 08:32:32 +08:00
|
|
|
|
2019-02-09 22:38:52 +08:00
|
|
|
@property
|
|
|
|
def _css(self):
|
2019-02-23 23:41:56 +08:00
|
|
|
css = defaultdict(list)
|
|
|
|
for css_list in self._css_lists:
|
|
|
|
for medium, sublist in css_list.items():
|
|
|
|
css[medium].append(sublist)
|
|
|
|
return {medium: self.merge(*lists) for medium, lists in css.items()}
|
2019-02-09 22:38:52 +08:00
|
|
|
|
|
|
|
@property
|
|
|
|
def _js(self):
|
2019-02-23 23:41:56 +08:00
|
|
|
return self.merge(*self._js_lists)
|
2019-02-09 22:38:52 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
def render(self):
|
2017-04-26 12:04:10 +08:00
|
|
|
return mark_safe(
|
|
|
|
"\n".join(
|
|
|
|
chain.from_iterable(
|
|
|
|
getattr(self, "render_" + name)() for name in MEDIA_TYPES
|
|
|
|
)
|
2022-02-04 03:24:19 +08:00
|
|
|
)
|
|
|
|
)
|
2008-08-25 08:32:32 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
def render_js(self):
|
2014-09-04 20:15:09 +08:00
|
|
|
return [
|
2022-01-23 00:21:57 +08:00
|
|
|
path.__html__()
|
|
|
|
if hasattr(path, "__html__")
|
|
|
|
else format_html('<script src="{}"></script>', self.absolute_path(path))
|
2014-09-04 20:15:09 +08:00
|
|
|
for path in self._js
|
|
|
|
]
|
2008-08-25 08:32:32 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
def render_css(self):
|
|
|
|
# To keep rendering order consistent, we can't just iterate over items().
|
|
|
|
# We need to sort the keys, and iterate over the sorted list.
|
2017-05-28 07:08:46 +08:00
|
|
|
media = sorted(self._css)
|
2017-04-26 12:04:10 +08:00
|
|
|
return chain.from_iterable(
|
|
|
|
[
|
2022-01-23 00:21:57 +08:00
|
|
|
path.__html__()
|
|
|
|
if hasattr(path, "__html__")
|
|
|
|
else format_html(
|
2022-01-21 22:46:37 +08:00
|
|
|
'<link href="{}" media="{}" rel="stylesheet">',
|
2014-09-04 20:15:09 +08:00
|
|
|
self.absolute_path(path),
|
|
|
|
medium,
|
|
|
|
)
|
|
|
|
for path in self._css[medium]
|
2017-04-26 12:04:10 +08:00
|
|
|
]
|
|
|
|
for medium in media
|
|
|
|
)
|
2008-08-25 08:32:32 +08:00
|
|
|
|
2015-11-07 19:24:38 +08:00
|
|
|
def absolute_path(self, path):
|
|
|
|
"""
|
|
|
|
Given a relative or absolute path to a static asset, return an absolute
|
|
|
|
path. An absolute path will be returned unchanged while a relative path
|
|
|
|
will be passed to django.templatetags.static.static().
|
|
|
|
"""
|
2012-06-08 00:08:47 +08:00
|
|
|
if path.startswith(("http://", "https://", "/")):
|
2008-07-19 07:54:34 +08:00
|
|
|
return path
|
2015-11-07 19:24:38 +08:00
|
|
|
return static(path)
|
2008-07-19 07:54:34 +08:00
|
|
|
|
|
|
|
def __getitem__(self, name):
|
2017-01-25 05:23:56 +08:00
|
|
|
"""Return a Media object that only contains media of the given type."""
|
2008-07-19 07:54:34 +08:00
|
|
|
if name in MEDIA_TYPES:
|
2009-04-11 01:07:25 +08:00
|
|
|
return Media(**{str(name): getattr(self, "_" + name)})
|
2008-07-19 07:54:34 +08:00
|
|
|
raise KeyError('Unknown media type "%s"' % name)
|
|
|
|
|
2017-07-20 23:06:30 +08:00
|
|
|
@staticmethod
|
2019-02-23 23:41:56 +08:00
|
|
|
def merge(*lists):
|
2017-07-20 23:06:30 +08:00
|
|
|
"""
|
2019-02-23 23:41:56 +08:00
|
|
|
Merge lists while trying to keep the relative order of the elements.
|
|
|
|
Warn if the lists have the same elements in a different relative order.
|
2008-08-25 08:32:32 +08:00
|
|
|
|
2017-07-20 23:06:30 +08:00
|
|
|
For static assets it can be important to have them included in the DOM
|
|
|
|
in a certain order. In JavaScript you may not be able to reference a
|
|
|
|
global or in CSS you might want to override a style.
|
|
|
|
"""
|
2019-02-23 23:41:56 +08:00
|
|
|
dependency_graph = defaultdict(set)
|
|
|
|
all_items = OrderedSet()
|
|
|
|
for list_ in filter(None, lists):
|
|
|
|
head = list_[0]
|
|
|
|
# The first items depend on nothing but have to be part of the
|
|
|
|
# dependency graph to be included in the result.
|
|
|
|
dependency_graph.setdefault(head, set())
|
|
|
|
for item in list_:
|
|
|
|
all_items.add(item)
|
|
|
|
# No self dependencies
|
|
|
|
if head != item:
|
|
|
|
dependency_graph[item].add(head)
|
|
|
|
head = item
|
|
|
|
try:
|
|
|
|
return stable_topological_sort(all_items, dependency_graph)
|
|
|
|
except CyclicDependencyError:
|
|
|
|
warnings.warn(
|
|
|
|
"Detected duplicate Media files in an opposite order: {}".format(
|
2020-05-12 14:52:23 +08:00
|
|
|
", ".join(repr(list_) for list_ in lists)
|
2019-02-23 23:41:56 +08:00
|
|
|
),
|
|
|
|
MediaOrderConflictWarning,
|
|
|
|
)
|
|
|
|
return list(all_items)
|
2008-07-19 07:54:34 +08:00
|
|
|
|
|
|
|
def __add__(self, other):
|
|
|
|
combined = Media()
|
2020-08-30 20:44:39 +08:00
|
|
|
combined._css_lists = self._css_lists[:]
|
|
|
|
combined._js_lists = self._js_lists[:]
|
|
|
|
for item in other._css_lists:
|
|
|
|
if item and item not in self._css_lists:
|
|
|
|
combined._css_lists.append(item)
|
|
|
|
for item in other._js_lists:
|
|
|
|
if item and item not in self._js_lists:
|
|
|
|
combined._js_lists.append(item)
|
2008-07-19 07:54:34 +08:00
|
|
|
return combined
|
|
|
|
|
2013-11-03 05:34:05 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
def media_property(cls):
|
|
|
|
def _media(self):
|
|
|
|
# Get the media property of the superclass, if it exists
|
2012-08-06 22:58:54 +08:00
|
|
|
sup_cls = super(cls, self)
|
|
|
|
try:
|
|
|
|
base = sup_cls.media
|
|
|
|
except AttributeError:
|
2008-07-19 07:54:34 +08:00
|
|
|
base = Media()
|
2008-08-25 08:32:32 +08:00
|
|
|
|
|
|
|
# Get the media definition for this class
|
2008-07-19 07:54:34 +08:00
|
|
|
definition = getattr(cls, "Media", None)
|
|
|
|
if definition:
|
|
|
|
extend = getattr(definition, "extend", True)
|
|
|
|
if extend:
|
2013-10-11 19:25:14 +08:00
|
|
|
if extend is True:
|
2008-07-19 07:54:34 +08:00
|
|
|
m = base
|
|
|
|
else:
|
|
|
|
m = Media()
|
|
|
|
for medium in extend:
|
|
|
|
m = m + base[medium]
|
|
|
|
return m + Media(definition)
|
2018-03-04 02:35:09 +08:00
|
|
|
return Media(definition)
|
|
|
|
return base
|
2022-02-04 03:24:19 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
return property(_media)
|
2008-08-25 08:32:32 +08:00
|
|
|
|
2013-11-03 05:34:05 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
class MediaDefiningClass(type):
|
2013-07-21 03:25:27 +08:00
|
|
|
"""
|
|
|
|
Metaclass for classes that can have media definitions.
|
|
|
|
"""
|
2022-02-04 03:24:19 +08:00
|
|
|
|
2013-07-21 03:25:27 +08:00
|
|
|
def __new__(mcs, name, bases, attrs):
|
2019-11-28 22:34:29 +08:00
|
|
|
new_class = super().__new__(mcs, name, bases, attrs)
|
2013-07-21 03:25:27 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
if "media" not in attrs:
|
|
|
|
new_class.media = media_property(new_class)
|
2013-07-21 03:25:27 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
return new_class
|
2008-08-25 08:32:32 +08:00
|
|
|
|
2013-11-03 04:12:09 +08:00
|
|
|
|
2017-01-07 19:11:46 +08:00
|
|
|
class Widget(metaclass=MediaDefiningClass):
|
2013-11-03 03:27:47 +08:00
|
|
|
needs_multipart_form = False # Determines does this widget need multipart form
|
2010-05-21 22:07:54 +08:00
|
|
|
is_localized = False
|
2010-10-01 10:02:58 +08:00
|
|
|
is_required = False
|
2015-04-08 23:59:15 +08:00
|
|
|
supports_microseconds = True
|
2022-01-14 07:08:38 +08:00
|
|
|
use_fieldset = False
|
2006-12-01 01:07:40 +08:00
|
|
|
|
2006-10-29 04:34:37 +08:00
|
|
|
def __init__(self, attrs=None):
|
2018-03-04 02:35:09 +08:00
|
|
|
self.attrs = {} if attrs is None else attrs.copy()
|
2006-10-29 04:34:37 +08:00
|
|
|
|
2007-10-04 09:12:00 +08:00
|
|
|
def __deepcopy__(self, memo):
|
|
|
|
obj = copy.copy(self)
|
|
|
|
obj.attrs = self.attrs.copy()
|
|
|
|
memo[id(self)] = obj
|
|
|
|
return obj
|
|
|
|
|
2014-02-26 23:03:51 +08:00
|
|
|
@property
|
|
|
|
def is_hidden(self):
|
|
|
|
return self.input_type == "hidden" if hasattr(self, "input_type") else False
|
|
|
|
|
2016-12-28 06:00:56 +08:00
|
|
|
def subwidgets(self, name, value, attrs=None):
|
|
|
|
context = self.get_context(name, value, attrs)
|
|
|
|
yield context["widget"]
|
2011-12-08 06:31:39 +08:00
|
|
|
|
2016-12-28 06:00:56 +08:00
|
|
|
def format_value(self, value):
|
2011-12-08 06:31:39 +08:00
|
|
|
"""
|
2016-12-28 06:00:56 +08:00
|
|
|
Return a value as it should appear when rendered in a template.
|
|
|
|
"""
|
2017-03-09 22:42:47 +08:00
|
|
|
if value == "" or value is None:
|
|
|
|
return None
|
2016-12-28 06:00:56 +08:00
|
|
|
if self.is_localized:
|
|
|
|
return formats.localize_input(value)
|
2017-04-22 01:52:26 +08:00
|
|
|
return str(value)
|
2016-12-28 06:00:56 +08:00
|
|
|
|
2017-03-20 21:42:59 +08:00
|
|
|
def get_context(self, name, value, attrs):
|
2020-05-06 07:43:13 +08:00
|
|
|
return {
|
|
|
|
"widget": {
|
|
|
|
"name": name,
|
|
|
|
"is_hidden": self.is_hidden,
|
|
|
|
"required": self.is_required,
|
|
|
|
"value": self.format_value(value),
|
|
|
|
"attrs": self.build_attrs(self.attrs, attrs),
|
|
|
|
"template_name": self.template_name,
|
|
|
|
},
|
2016-12-28 06:00:56 +08:00
|
|
|
}
|
2011-12-08 06:31:39 +08:00
|
|
|
|
2016-12-28 06:00:56 +08:00
|
|
|
def render(self, name, value, attrs=None, renderer=None):
|
2017-01-21 05:04:05 +08:00
|
|
|
"""Render the widget as an HTML string."""
|
2016-12-28 06:00:56 +08:00
|
|
|
context = self.get_context(name, value, attrs)
|
|
|
|
return self._render(self.template_name, context, renderer)
|
|
|
|
|
|
|
|
def _render(self, template_name, context, renderer=None):
|
|
|
|
if renderer is None:
|
|
|
|
renderer = get_default_renderer()
|
|
|
|
return mark_safe(renderer.render(template_name, context))
|
2006-10-29 04:34:37 +08:00
|
|
|
|
2016-12-28 06:00:56 +08:00
|
|
|
def build_attrs(self, base_attrs, extra_attrs=None):
|
2017-01-25 05:23:56 +08:00
|
|
|
"""Build an attribute dictionary."""
|
2017-12-11 20:08:45 +08:00
|
|
|
return {**base_attrs, **(extra_attrs or {})}
|
2006-11-16 04:16:04 +08:00
|
|
|
|
2007-08-06 21:58:56 +08:00
|
|
|
def value_from_datadict(self, data, files, name):
|
2006-11-30 01:00:34 +08:00
|
|
|
"""
|
2017-01-25 05:23:56 +08:00
|
|
|
Given a dictionary of data and this widget's name, return the value
|
|
|
|
of this widget or None if it's not provided.
|
2006-11-30 01:00:34 +08:00
|
|
|
"""
|
2015-05-14 02:51:18 +08:00
|
|
|
return data.get(name)
|
2006-11-30 01:00:34 +08:00
|
|
|
|
2016-09-07 05:41:54 +08:00
|
|
|
def value_omitted_from_data(self, data, files, name):
|
|
|
|
return name not in data
|
|
|
|
|
2006-11-29 09:40:27 +08:00
|
|
|
def id_for_label(self, id_):
|
|
|
|
"""
|
2022-01-07 04:34:31 +08:00
|
|
|
Return the HTML ID attribute of this Widget for use by a <label>, given
|
|
|
|
the ID of the field. Return an empty string if no ID is available.
|
2006-11-29 09:40:27 +08:00
|
|
|
|
|
|
|
This hook is necessary because some widgets have multiple HTML
|
|
|
|
elements and, thus, multiple IDs. In that case, this method should
|
|
|
|
return an ID value that corresponds to the first ID in the widget's
|
|
|
|
tags.
|
|
|
|
"""
|
|
|
|
return id_
|
|
|
|
|
2016-08-13 01:59:01 +08:00
|
|
|
def use_required_attribute(self, initial):
|
|
|
|
return not self.is_hidden
|
|
|
|
|
2013-11-03 05:34:05 +08:00
|
|
|
|
2006-11-02 07:53:37 +08:00
|
|
|
class Input(Widget):
|
2006-11-16 06:08:22 +08:00
|
|
|
"""
|
2016-12-28 06:00:56 +08:00
|
|
|
Base class for all <input> widgets.
|
2006-11-16 06:08:22 +08:00
|
|
|
"""
|
2022-02-04 03:24:19 +08:00
|
|
|
|
2013-11-03 03:27:47 +08:00
|
|
|
input_type = None # Subclasses must define this.
|
2016-12-28 06:00:56 +08:00
|
|
|
template_name = "django/forms/widgets/input.html"
|
2006-12-13 14:26:04 +08:00
|
|
|
|
2016-12-28 06:00:56 +08:00
|
|
|
def __init__(self, attrs=None):
|
|
|
|
if attrs is not None:
|
2017-01-10 19:30:10 +08:00
|
|
|
attrs = attrs.copy()
|
2016-12-28 06:00:56 +08:00
|
|
|
self.input_type = attrs.pop("type", self.input_type)
|
2017-01-21 21:13:44 +08:00
|
|
|
super().__init__(attrs)
|
2010-05-21 22:07:54 +08:00
|
|
|
|
2017-03-20 21:42:59 +08:00
|
|
|
def get_context(self, name, value, attrs):
|
2017-01-21 21:13:44 +08:00
|
|
|
context = super().get_context(name, value, attrs)
|
2016-12-28 06:00:56 +08:00
|
|
|
context["widget"]["type"] = self.input_type
|
|
|
|
return context
|
2006-10-29 04:34:37 +08:00
|
|
|
|
2012-09-11 01:21:29 +08:00
|
|
|
|
2006-11-02 07:53:37 +08:00
|
|
|
class TextInput(Input):
|
|
|
|
input_type = "text"
|
2016-12-28 06:00:56 +08:00
|
|
|
template_name = "django/forms/widgets/text.html"
|
2012-09-11 01:21:29 +08:00
|
|
|
|
|
|
|
|
2016-12-28 06:00:56 +08:00
|
|
|
class NumberInput(Input):
|
2013-02-23 16:45:56 +08:00
|
|
|
input_type = "number"
|
2016-12-28 06:00:56 +08:00
|
|
|
template_name = "django/forms/widgets/number.html"
|
2013-02-23 16:45:56 +08:00
|
|
|
|
|
|
|
|
2016-12-28 06:00:56 +08:00
|
|
|
class EmailInput(Input):
|
2013-01-28 21:12:56 +08:00
|
|
|
input_type = "email"
|
2016-12-28 06:00:56 +08:00
|
|
|
template_name = "django/forms/widgets/email.html"
|
2013-01-28 21:12:56 +08:00
|
|
|
|
|
|
|
|
2016-12-28 06:00:56 +08:00
|
|
|
class URLInput(Input):
|
2013-01-28 21:24:48 +08:00
|
|
|
input_type = "url"
|
2016-12-28 06:00:56 +08:00
|
|
|
template_name = "django/forms/widgets/url.html"
|
2013-01-28 21:24:48 +08:00
|
|
|
|
|
|
|
|
2016-12-28 06:00:56 +08:00
|
|
|
class PasswordInput(Input):
|
2006-11-02 07:53:37 +08:00
|
|
|
input_type = "password"
|
2016-12-28 06:00:56 +08:00
|
|
|
template_name = "django/forms/widgets/password.html"
|
2006-11-02 07:53:37 +08:00
|
|
|
|
2010-08-06 22:25:58 +08:00
|
|
|
def __init__(self, attrs=None, render_value=False):
|
2017-01-21 21:13:44 +08:00
|
|
|
super().__init__(attrs)
|
2007-02-15 13:05:43 +08:00
|
|
|
self.render_value = render_value
|
|
|
|
|
2016-12-28 06:00:56 +08:00
|
|
|
def get_context(self, name, value, attrs):
|
2013-10-17 16:17:41 +08:00
|
|
|
if not self.render_value:
|
2013-10-23 18:09:29 +08:00
|
|
|
value = None
|
2017-01-21 21:13:44 +08:00
|
|
|
return super().get_context(name, value, attrs)
|
2007-02-15 13:05:43 +08:00
|
|
|
|
2013-11-03 05:34:05 +08:00
|
|
|
|
2006-11-02 07:53:37 +08:00
|
|
|
class HiddenInput(Input):
|
|
|
|
input_type = "hidden"
|
2016-12-28 06:00:56 +08:00
|
|
|
template_name = "django/forms/widgets/hidden.html"
|
2006-11-02 07:53:37 +08:00
|
|
|
|
2013-11-03 05:34:05 +08:00
|
|
|
|
2007-01-09 13:12:25 +08:00
|
|
|
class MultipleHiddenInput(HiddenInput):
|
|
|
|
"""
|
2017-01-25 05:23:56 +08:00
|
|
|
Handle <input type="hidden"> for fields that have a list
|
2007-01-09 13:12:25 +08:00
|
|
|
of values.
|
|
|
|
"""
|
2022-02-04 03:24:19 +08:00
|
|
|
|
2016-12-28 06:00:56 +08:00
|
|
|
template_name = "django/forms/widgets/multiple_hidden.html"
|
|
|
|
|
2017-03-20 21:42:59 +08:00
|
|
|
def get_context(self, name, value, attrs):
|
2017-01-21 21:13:44 +08:00
|
|
|
context = super().get_context(name, value, attrs)
|
2016-12-28 06:00:56 +08:00
|
|
|
final_attrs = context["widget"]["attrs"]
|
|
|
|
id_ = context["widget"]["attrs"].get("id")
|
|
|
|
|
|
|
|
subwidgets = []
|
|
|
|
for index, value_ in enumerate(context["widget"]["value"]):
|
|
|
|
widget_attrs = final_attrs.copy()
|
2010-01-10 06:14:01 +08:00
|
|
|
if id_:
|
|
|
|
# An ID attribute was given. Add a numeric index as a suffix
|
|
|
|
# so that the inputs don't all have the same ID attribute.
|
2016-12-28 06:00:56 +08:00
|
|
|
widget_attrs["id"] = "%s_%s" % (id_, index)
|
|
|
|
widget = HiddenInput()
|
|
|
|
widget.is_required = self.is_required
|
|
|
|
subwidgets.append(widget.get_context(name, value_, widget_attrs)["widget"])
|
|
|
|
|
|
|
|
context["widget"]["subwidgets"] = subwidgets
|
|
|
|
return context
|
2007-01-09 13:12:25 +08:00
|
|
|
|
2007-08-06 21:58:56 +08:00
|
|
|
def value_from_datadict(self, data, files, name):
|
2016-07-19 02:04:35 +08:00
|
|
|
try:
|
|
|
|
getter = data.getlist
|
|
|
|
except AttributeError:
|
|
|
|
getter = data.get
|
|
|
|
return getter(name)
|
2007-01-13 12:51:57 +08:00
|
|
|
|
2016-12-28 06:00:56 +08:00
|
|
|
def format_value(self, value):
|
|
|
|
return [] if value is None else value
|
|
|
|
|
2013-11-03 05:34:05 +08:00
|
|
|
|
2006-11-02 07:53:37 +08:00
|
|
|
class FileInput(Input):
|
|
|
|
input_type = "file"
|
2007-09-15 18:12:05 +08:00
|
|
|
needs_multipart_form = True
|
2016-12-28 06:00:56 +08:00
|
|
|
template_name = "django/forms/widgets/file.html"
|
2006-11-02 07:53:37 +08:00
|
|
|
|
2016-12-28 06:00:56 +08:00
|
|
|
def format_value(self, value):
|
|
|
|
"""File input never renders a value."""
|
|
|
|
return
|
2007-10-21 22:50:47 +08:00
|
|
|
|
2007-08-06 21:58:56 +08:00
|
|
|
def value_from_datadict(self, data, files, name):
|
|
|
|
"File widgets take data from FILES, not POST"
|
2015-05-14 02:51:18 +08:00
|
|
|
return files.get(name)
|
2008-08-25 08:32:32 +08:00
|
|
|
|
2016-09-07 05:41:54 +08:00
|
|
|
def value_omitted_from_data(self, data, files, name):
|
|
|
|
return name not in files
|
|
|
|
|
2020-01-05 15:51:33 +08:00
|
|
|
def use_required_attribute(self, initial):
|
|
|
|
return super().use_required_attribute(initial) and not initial
|
|
|
|
|
2007-08-06 21:58:56 +08:00
|
|
|
|
2010-10-01 10:02:58 +08:00
|
|
|
FILE_INPUT_CONTRADICTION = object()
|
|
|
|
|
2013-11-03 05:34:05 +08:00
|
|
|
|
2010-10-01 10:02:58 +08:00
|
|
|
class ClearableFileInput(FileInput):
|
2017-01-27 03:58:33 +08:00
|
|
|
clear_checkbox_label = _("Clear")
|
|
|
|
initial_text = _("Currently")
|
|
|
|
input_text = _("Change")
|
2016-12-28 06:00:56 +08:00
|
|
|
template_name = "django/forms/widgets/clearable_file_input.html"
|
2010-10-01 10:02:58 +08:00
|
|
|
|
|
|
|
def clear_checkbox_name(self, name):
|
|
|
|
"""
|
|
|
|
Given the name of the file input, return the name of the clear checkbox
|
|
|
|
input.
|
|
|
|
"""
|
|
|
|
return name + "-clear"
|
|
|
|
|
|
|
|
def clear_checkbox_id(self, name):
|
|
|
|
"""
|
|
|
|
Given the name of the clear checkbox input, return the HTML id for it.
|
|
|
|
"""
|
|
|
|
return name + "_id"
|
|
|
|
|
2013-10-25 15:13:00 +08:00
|
|
|
def is_initial(self, value):
|
|
|
|
"""
|
|
|
|
Return whether value is considered to be initial value.
|
|
|
|
"""
|
2016-02-15 05:28:26 +08:00
|
|
|
return bool(value and getattr(value, "url", False))
|
2013-10-25 15:13:00 +08:00
|
|
|
|
2016-12-28 06:00:56 +08:00
|
|
|
def format_value(self, value):
|
2013-10-25 15:13:00 +08:00
|
|
|
"""
|
2016-12-28 06:00:56 +08:00
|
|
|
Return the file object if it has a defined url attribute.
|
2013-10-25 15:13:00 +08:00
|
|
|
"""
|
2016-12-28 06:00:56 +08:00
|
|
|
if self.is_initial(value):
|
|
|
|
return value
|
|
|
|
|
2017-03-20 21:42:59 +08:00
|
|
|
def get_context(self, name, value, attrs):
|
2017-01-21 21:13:44 +08:00
|
|
|
context = super().get_context(name, value, attrs)
|
2016-12-28 06:00:56 +08:00
|
|
|
checkbox_name = self.clear_checkbox_name(name)
|
|
|
|
checkbox_id = self.clear_checkbox_id(checkbox_name)
|
2017-07-19 23:24:27 +08:00
|
|
|
context["widget"].update(
|
|
|
|
{
|
2016-12-28 06:00:56 +08:00
|
|
|
"checkbox_name": checkbox_name,
|
|
|
|
"checkbox_id": checkbox_id,
|
|
|
|
"is_initial": self.is_initial(value),
|
2010-10-01 10:02:58 +08:00
|
|
|
"input_text": self.input_text,
|
2016-12-28 06:00:56 +08:00
|
|
|
"initial_text": self.initial_text,
|
2010-10-01 10:02:58 +08:00
|
|
|
"clear_checkbox_label": self.clear_checkbox_label,
|
2016-12-28 06:00:56 +08:00
|
|
|
}
|
|
|
|
)
|
2022-08-24 17:05:01 +08:00
|
|
|
context["widget"]["attrs"].setdefault("disabled", False)
|
2016-12-28 06:00:56 +08:00
|
|
|
return context
|
2010-10-01 10:02:58 +08:00
|
|
|
|
|
|
|
def value_from_datadict(self, data, files, name):
|
2017-01-21 21:13:44 +08:00
|
|
|
upload = super().value_from_datadict(data, files, name)
|
2010-10-01 10:02:58 +08:00
|
|
|
if not self.is_required and CheckboxInput().value_from_datadict(
|
2013-11-03 05:34:05 +08:00
|
|
|
data, files, self.clear_checkbox_name(name)
|
|
|
|
):
|
|
|
|
|
2010-10-01 10:02:58 +08:00
|
|
|
if upload:
|
|
|
|
# If the user contradicts themselves (uploads a new file AND
|
|
|
|
# checks the "clear" checkbox), we return a unique marker
|
|
|
|
# object that FileField will turn into a ValidationError.
|
|
|
|
return FILE_INPUT_CONTRADICTION
|
|
|
|
# False signals to clear any existing value, as opposed to just None
|
|
|
|
return False
|
|
|
|
return upload
|
|
|
|
|
2017-02-04 00:11:39 +08:00
|
|
|
def value_omitted_from_data(self, data, files, name):
|
|
|
|
return (
|
|
|
|
super().value_omitted_from_data(data, files, name)
|
|
|
|
and self.clear_checkbox_name(name) not in data
|
|
|
|
)
|
|
|
|
|
2013-11-03 05:34:05 +08:00
|
|
|
|
2006-10-29 04:34:37 +08:00
|
|
|
class Textarea(Widget):
|
2016-12-28 06:00:56 +08:00
|
|
|
template_name = "django/forms/widgets/textarea.html"
|
|
|
|
|
2007-04-09 09:22:45 +08:00
|
|
|
def __init__(self, attrs=None):
|
2014-02-28 02:36:44 +08:00
|
|
|
# Use slightly better defaults than HTML's 20x2 box
|
2009-09-11 00:30:41 +08:00
|
|
|
default_attrs = {"cols": "40", "rows": "10"}
|
2007-04-09 09:22:45 +08:00
|
|
|
if attrs:
|
2009-09-11 00:30:41 +08:00
|
|
|
default_attrs.update(attrs)
|
2017-01-21 21:13:44 +08:00
|
|
|
super().__init__(default_attrs)
|
2007-04-09 09:22:45 +08:00
|
|
|
|
2009-03-23 00:13:06 +08:00
|
|
|
|
2013-10-09 03:16:58 +08:00
|
|
|
class DateTimeBaseInput(TextInput):
|
|
|
|
format_key = ""
|
2014-05-07 09:14:06 +08:00
|
|
|
supports_microseconds = False
|
2013-10-22 18:21:07 +08:00
|
|
|
|
2009-03-23 00:13:06 +08:00
|
|
|
def __init__(self, attrs=None, format=None):
|
2017-01-21 21:13:44 +08:00
|
|
|
super().__init__(attrs)
|
2017-11-06 23:23:29 +08:00
|
|
|
self.format = format or None
|
2009-03-23 00:13:06 +08:00
|
|
|
|
2016-04-24 01:15:45 +08:00
|
|
|
def format_value(self, value):
|
2016-03-29 06:33:29 +08:00
|
|
|
return formats.localize_input(
|
|
|
|
value, self.format or formats.get_format(self.format_key)[0]
|
|
|
|
)
|
2009-04-28 21:19:30 +08:00
|
|
|
|
2007-10-21 22:50:47 +08:00
|
|
|
|
2013-10-09 03:16:58 +08:00
|
|
|
class DateInput(DateTimeBaseInput):
|
|
|
|
format_key = "DATE_INPUT_FORMATS"
|
2016-12-28 06:00:56 +08:00
|
|
|
template_name = "django/forms/widgets/date.html"
|
2007-10-21 22:50:47 +08:00
|
|
|
|
2009-04-28 21:19:30 +08:00
|
|
|
|
2013-10-09 03:16:58 +08:00
|
|
|
class DateTimeInput(DateTimeBaseInput):
|
|
|
|
format_key = "DATETIME_INPUT_FORMATS"
|
2016-12-28 06:00:56 +08:00
|
|
|
template_name = "django/forms/widgets/datetime.html"
|
2009-03-23 00:13:06 +08:00
|
|
|
|
2008-08-24 01:33:09 +08:00
|
|
|
|
2013-10-09 03:16:58 +08:00
|
|
|
class TimeInput(DateTimeBaseInput):
|
|
|
|
format_key = "TIME_INPUT_FORMATS"
|
2016-12-28 06:00:56 +08:00
|
|
|
template_name = "django/forms/widgets/time.html"
|
2009-04-28 21:19:30 +08:00
|
|
|
|
2012-04-29 20:25:06 +08:00
|
|
|
|
|
|
|
# Defined at module level so that CheckboxInput is picklable (#17976)
|
|
|
|
def boolean_check(v):
|
|
|
|
return not (v is False or v is None or v == "")
|
|
|
|
|
|
|
|
|
2016-12-28 06:00:56 +08:00
|
|
|
class CheckboxInput(Input):
|
|
|
|
input_type = "checkbox"
|
|
|
|
template_name = "django/forms/widgets/checkbox.html"
|
|
|
|
|
2011-11-21 18:11:06 +08:00
|
|
|
def __init__(self, attrs=None, check_test=None):
|
2017-01-21 21:13:44 +08:00
|
|
|
super().__init__(attrs)
|
2006-12-01 06:06:08 +08:00
|
|
|
# check_test is a callable that takes a value and returns True
|
|
|
|
# if the checkbox should be checked for that value.
|
2012-04-29 20:25:06 +08:00
|
|
|
self.check_test = boolean_check if check_test is None else check_test
|
2006-12-01 06:06:08 +08:00
|
|
|
|
2016-12-28 06:00:56 +08:00
|
|
|
def format_value(self, value):
|
|
|
|
"""Only return the 'value' attribute if value isn't empty."""
|
|
|
|
if value is True or value is False or value is None or value == "":
|
|
|
|
return
|
2017-04-22 01:52:26 +08:00
|
|
|
return str(value)
|
2016-12-28 06:00:56 +08:00
|
|
|
|
2017-03-20 21:42:59 +08:00
|
|
|
def get_context(self, name, value, attrs):
|
2016-12-28 06:00:56 +08:00
|
|
|
if self.check_test(value):
|
2019-12-10 00:54:40 +08:00
|
|
|
attrs = {**(attrs or {}), "checked": True}
|
2017-01-21 21:13:44 +08:00
|
|
|
return super().get_context(name, value, attrs)
|
2006-11-02 07:53:37 +08:00
|
|
|
|
2007-10-20 20:21:07 +08:00
|
|
|
def value_from_datadict(self, data, files, name):
|
|
|
|
if name not in data:
|
|
|
|
# A missing value means False because HTML form submission does not
|
|
|
|
# send results for unselected checkboxes.
|
|
|
|
return False
|
2010-02-24 07:37:45 +08:00
|
|
|
value = data.get(name)
|
|
|
|
# Translate true and false strings to boolean values.
|
2012-09-08 02:37:21 +08:00
|
|
|
values = {"true": True, "false": False}
|
2016-12-29 23:27:49 +08:00
|
|
|
if isinstance(value, str):
|
2010-02-24 07:37:45 +08:00
|
|
|
value = values.get(value.lower(), value)
|
2012-10-27 02:44:00 +08:00
|
|
|
return bool(value)
|
2007-10-20 20:21:07 +08:00
|
|
|
|
2016-09-07 05:41:54 +08:00
|
|
|
def value_omitted_from_data(self, data, files, name):
|
|
|
|
# HTML checkboxes don't appear in POST data if not checked, so it's
|
|
|
|
# never known if the value is actually omitted.
|
|
|
|
return False
|
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
|
2016-12-28 06:00:56 +08:00
|
|
|
class ChoiceWidget(Widget):
|
2011-09-18 12:09:44 +08:00
|
|
|
allow_multiple_selected = False
|
2016-12-28 06:00:56 +08:00
|
|
|
input_type = None
|
|
|
|
template_name = None
|
|
|
|
option_template_name = None
|
|
|
|
add_id_index = True
|
|
|
|
checked_attribute = {"checked": True}
|
|
|
|
option_inherits_attrs = True
|
2011-09-18 12:09:44 +08:00
|
|
|
|
2006-11-02 07:53:37 +08:00
|
|
|
def __init__(self, attrs=None, choices=()):
|
2017-01-21 21:13:44 +08:00
|
|
|
super().__init__(attrs)
|
2007-01-28 05:30:26 +08:00
|
|
|
# choices can be any iterable, but we may need to render this widget
|
|
|
|
# multiple times. Thus, collapse it into a list so it can be consumed
|
|
|
|
# more than once.
|
|
|
|
self.choices = list(choices)
|
2006-11-02 07:53:37 +08:00
|
|
|
|
2015-07-09 08:03:11 +08:00
|
|
|
def __deepcopy__(self, memo):
|
|
|
|
obj = copy.copy(self)
|
|
|
|
obj.attrs = self.attrs.copy()
|
|
|
|
obj.choices = copy.copy(self.choices)
|
|
|
|
memo[id(self)] = obj
|
|
|
|
return obj
|
|
|
|
|
2016-12-28 06:00:56 +08:00
|
|
|
def subwidgets(self, name, value, attrs=None):
|
|
|
|
"""
|
|
|
|
Yield all "subwidgets" of this widget. Used to enable iterating
|
|
|
|
options from a BoundField for choice widgets.
|
|
|
|
"""
|
|
|
|
value = self.format_value(value)
|
2017-02-24 09:06:01 +08:00
|
|
|
yield from self.options(name, value, attrs)
|
2016-12-28 06:00:56 +08:00
|
|
|
|
|
|
|
def options(self, name, value, attrs=None):
|
2021-12-21 16:14:58 +08:00
|
|
|
"""Yield a flat list of options for this widget."""
|
2016-12-28 06:00:56 +08:00
|
|
|
for group in self.optgroups(name, value, attrs):
|
2017-02-24 09:06:01 +08:00
|
|
|
yield from group[1]
|
2016-12-28 06:00:56 +08:00
|
|
|
|
|
|
|
def optgroups(self, name, value, attrs=None):
|
|
|
|
"""Return a list of optgroups for this widget."""
|
2017-05-03 19:21:44 +08:00
|
|
|
groups = []
|
2016-12-28 06:00:56 +08:00
|
|
|
has_selected = False
|
|
|
|
|
2017-08-31 20:04:36 +08:00
|
|
|
for index, (option_value, option_label) in enumerate(self.choices):
|
2016-12-28 06:00:56 +08:00
|
|
|
if option_value is None:
|
|
|
|
option_value = ""
|
|
|
|
|
2017-05-03 19:21:44 +08:00
|
|
|
subgroup = []
|
2008-07-19 15:53:02 +08:00
|
|
|
if isinstance(option_label, (list, tuple)):
|
2017-05-03 19:21:44 +08:00
|
|
|
group_name = option_value
|
2016-12-28 06:00:56 +08:00
|
|
|
subindex = 0
|
|
|
|
choices = option_label
|
|
|
|
else:
|
2017-05-03 19:21:44 +08:00
|
|
|
group_name = None
|
2016-12-28 06:00:56 +08:00
|
|
|
subindex = None
|
|
|
|
choices = [(option_value, option_label)]
|
2017-05-03 19:21:44 +08:00
|
|
|
groups.append((group_name, subgroup, index))
|
2016-12-28 06:00:56 +08:00
|
|
|
|
|
|
|
for subvalue, sublabel in choices:
|
2021-01-28 04:10:11 +08:00
|
|
|
selected = (not has_selected or self.allow_multiple_selected) and str(
|
|
|
|
subvalue
|
|
|
|
) in value
|
2018-01-12 22:05:16 +08:00
|
|
|
has_selected |= selected
|
2016-12-28 06:00:56 +08:00
|
|
|
subgroup.append(
|
|
|
|
self.create_option(
|
2017-03-20 21:28:21 +08:00
|
|
|
name,
|
|
|
|
subvalue,
|
|
|
|
sublabel,
|
|
|
|
selected,
|
|
|
|
index,
|
|
|
|
subindex=subindex,
|
|
|
|
attrs=attrs,
|
2016-12-28 06:00:56 +08:00
|
|
|
)
|
2022-02-04 03:24:19 +08:00
|
|
|
)
|
2016-12-28 06:00:56 +08:00
|
|
|
if subindex is not None:
|
|
|
|
subindex += 1
|
|
|
|
return groups
|
|
|
|
|
|
|
|
def create_option(
|
|
|
|
self, name, value, label, selected, index, subindex=None, attrs=None
|
|
|
|
):
|
|
|
|
index = str(index) if subindex is None else "%s_%s" % (index, subindex)
|
|
|
|
option_attrs = (
|
|
|
|
self.build_attrs(self.attrs, attrs) if self.option_inherits_attrs else {}
|
2022-02-04 03:24:19 +08:00
|
|
|
)
|
2016-12-28 06:00:56 +08:00
|
|
|
if selected:
|
|
|
|
option_attrs.update(self.checked_attribute)
|
|
|
|
if "id" in option_attrs:
|
|
|
|
option_attrs["id"] = self.id_for_label(option_attrs["id"], index)
|
2017-01-24 08:13:49 +08:00
|
|
|
return {
|
|
|
|
"name": name,
|
2017-06-15 23:05:21 +08:00
|
|
|
"value": value,
|
2017-01-24 08:13:49 +08:00
|
|
|
"label": label,
|
|
|
|
"selected": selected,
|
|
|
|
"index": index,
|
|
|
|
"attrs": option_attrs,
|
|
|
|
"type": self.input_type,
|
|
|
|
"template_name": self.option_template_name,
|
2018-03-14 23:28:18 +08:00
|
|
|
"wrap_label": True,
|
2017-01-24 08:13:49 +08:00
|
|
|
}
|
2016-12-28 06:00:56 +08:00
|
|
|
|
2017-03-20 21:42:59 +08:00
|
|
|
def get_context(self, name, value, attrs):
|
2017-01-21 21:13:44 +08:00
|
|
|
context = super().get_context(name, value, attrs)
|
2016-12-28 06:00:56 +08:00
|
|
|
context["widget"]["optgroups"] = self.optgroups(
|
|
|
|
name, context["widget"]["value"], attrs
|
|
|
|
)
|
|
|
|
return context
|
|
|
|
|
|
|
|
def id_for_label(self, id_, index="0"):
|
|
|
|
"""
|
|
|
|
Use an incremented id for each option where the main widget
|
|
|
|
references the zero index.
|
|
|
|
"""
|
|
|
|
if id_ and self.add_id_index:
|
|
|
|
id_ = "%s_%s" % (id_, index)
|
|
|
|
return id_
|
|
|
|
|
|
|
|
def value_from_datadict(self, data, files, name):
|
|
|
|
getter = data.get
|
|
|
|
if self.allow_multiple_selected:
|
2017-09-07 20:16:21 +08:00
|
|
|
try:
|
2016-12-28 06:00:56 +08:00
|
|
|
getter = data.getlist
|
2017-09-07 20:16:21 +08:00
|
|
|
except AttributeError:
|
|
|
|
pass
|
2016-12-28 06:00:56 +08:00
|
|
|
return getter(name)
|
|
|
|
|
|
|
|
def format_value(self, value):
|
2017-02-21 20:27:29 +08:00
|
|
|
"""Return selected values as a list."""
|
2018-03-30 17:55:33 +08:00
|
|
|
if value is None and self.allow_multiple_selected:
|
|
|
|
return []
|
2016-12-28 06:00:56 +08:00
|
|
|
if not isinstance(value, (tuple, list)):
|
|
|
|
value = [value]
|
2017-02-21 20:27:29 +08:00
|
|
|
return [str(v) if v is not None else "" for v in value]
|
2016-12-28 06:00:56 +08:00
|
|
|
|
|
|
|
|
|
|
|
class Select(ChoiceWidget):
|
|
|
|
input_type = "select"
|
|
|
|
template_name = "django/forms/widgets/select.html"
|
|
|
|
option_template_name = "django/forms/widgets/select_option.html"
|
|
|
|
add_id_index = False
|
|
|
|
checked_attribute = {"selected": True}
|
|
|
|
option_inherits_attrs = False
|
|
|
|
|
2017-03-20 21:42:59 +08:00
|
|
|
def get_context(self, name, value, attrs):
|
2017-01-21 21:13:44 +08:00
|
|
|
context = super().get_context(name, value, attrs)
|
2016-12-28 06:00:56 +08:00
|
|
|
if self.allow_multiple_selected:
|
2018-01-21 00:19:06 +08:00
|
|
|
context["widget"]["attrs"]["multiple"] = True
|
2016-12-28 06:00:56 +08:00
|
|
|
return context
|
2008-07-19 15:53:02 +08:00
|
|
|
|
2016-11-05 19:27:44 +08:00
|
|
|
@staticmethod
|
|
|
|
def _choice_has_empty_value(choice):
|
|
|
|
"""Return True if the choice's value is empty string or None."""
|
|
|
|
value, _ = choice
|
2017-12-08 11:14:25 +08:00
|
|
|
return value is None or value == ""
|
2016-11-05 19:27:44 +08:00
|
|
|
|
|
|
|
def use_required_attribute(self, initial):
|
|
|
|
"""
|
|
|
|
Don't render 'required' if the first <option> has a value, as that's
|
|
|
|
invalid HTML.
|
|
|
|
"""
|
2017-01-21 21:13:44 +08:00
|
|
|
use_required_attribute = super().use_required_attribute(initial)
|
2016-11-05 19:27:44 +08:00
|
|
|
# 'required' is always okay for <select multiple>.
|
|
|
|
if self.allow_multiple_selected:
|
|
|
|
return use_required_attribute
|
|
|
|
|
|
|
|
first_choice = next(iter(self.choices), None)
|
|
|
|
return (
|
|
|
|
use_required_attribute
|
|
|
|
and first_choice is not None
|
|
|
|
and self._choice_has_empty_value(first_choice)
|
2022-02-04 03:24:19 +08:00
|
|
|
)
|
2016-11-05 19:27:44 +08:00
|
|
|
|
2013-11-03 05:34:05 +08:00
|
|
|
|
2007-01-24 13:23:19 +08:00
|
|
|
class NullBooleanSelect(Select):
|
|
|
|
"""
|
|
|
|
A Select Widget intended to be used with NullBooleanField.
|
|
|
|
"""
|
2022-02-04 03:24:19 +08:00
|
|
|
|
2007-01-24 13:23:19 +08:00
|
|
|
def __init__(self, attrs=None):
|
2016-03-29 06:33:29 +08:00
|
|
|
choices = (
|
2018-11-15 02:43:34 +08:00
|
|
|
("unknown", _("Unknown")),
|
|
|
|
("true", _("Yes")),
|
|
|
|
("false", _("No")),
|
2016-03-29 06:33:29 +08:00
|
|
|
)
|
2017-01-21 21:13:44 +08:00
|
|
|
super().__init__(attrs, choices)
|
2007-01-24 13:23:19 +08:00
|
|
|
|
2016-12-28 06:00:56 +08:00
|
|
|
def format_value(self, value):
|
2007-01-24 13:23:19 +08:00
|
|
|
try:
|
2018-11-15 02:43:34 +08:00
|
|
|
return {
|
|
|
|
True: "true",
|
|
|
|
False: "false",
|
|
|
|
"true": "true",
|
|
|
|
"false": "false",
|
|
|
|
# For backwards compatibility with Django < 2.2.
|
|
|
|
"2": "true",
|
|
|
|
"3": "false",
|
|
|
|
}[value]
|
2007-01-24 13:23:19 +08:00
|
|
|
except KeyError:
|
2018-11-15 02:43:34 +08:00
|
|
|
return "unknown"
|
2007-01-24 13:23:19 +08:00
|
|
|
|
2007-08-06 21:58:56 +08:00
|
|
|
def value_from_datadict(self, data, files, name):
|
2015-05-14 02:51:18 +08:00
|
|
|
value = data.get(name)
|
2016-03-29 06:33:29 +08:00
|
|
|
return {
|
|
|
|
True: True,
|
|
|
|
"True": True,
|
|
|
|
"False": False,
|
|
|
|
False: False,
|
2018-11-15 02:43:34 +08:00
|
|
|
"true": True,
|
|
|
|
"false": False,
|
|
|
|
# For backwards compatibility with Django < 2.2.
|
|
|
|
"2": True,
|
|
|
|
"3": False,
|
2016-03-29 06:33:29 +08:00
|
|
|
}.get(value)
|
2007-01-24 13:23:19 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
|
2008-07-19 15:53:02 +08:00
|
|
|
class SelectMultiple(Select):
|
2011-09-18 12:09:44 +08:00
|
|
|
allow_multiple_selected = True
|
|
|
|
|
2007-08-06 21:58:56 +08:00
|
|
|
def value_from_datadict(self, data, files, name):
|
2016-07-19 02:04:35 +08:00
|
|
|
try:
|
|
|
|
getter = data.getlist
|
|
|
|
except AttributeError:
|
|
|
|
getter = data.get
|
|
|
|
return getter(name)
|
2008-08-25 08:32:32 +08:00
|
|
|
|
2017-03-31 22:10:08 +08:00
|
|
|
def value_omitted_from_data(self, data, files, name):
|
|
|
|
# An unselected <select multiple> doesn't appear in POST data, so it's
|
|
|
|
# never known if the value is actually omitted.
|
|
|
|
return False
|
|
|
|
|
2006-12-13 14:26:04 +08:00
|
|
|
|
2016-12-28 06:00:56 +08:00
|
|
|
class RadioSelect(ChoiceWidget):
|
2013-04-13 08:02:28 +08:00
|
|
|
input_type = "radio"
|
2016-12-28 06:00:56 +08:00
|
|
|
template_name = "django/forms/widgets/radio.html"
|
|
|
|
option_template_name = "django/forms/widgets/radio_option.html"
|
2022-01-14 07:08:38 +08:00
|
|
|
use_fieldset = True
|
2013-04-13 08:02:28 +08:00
|
|
|
|
2021-05-13 15:26:54 +08:00
|
|
|
def id_for_label(self, id_, index=None):
|
|
|
|
"""
|
|
|
|
Don't include for="field_0" in <label> to improve accessibility when
|
|
|
|
using a screen reader, in addition clicking such a label would toggle
|
|
|
|
the first input.
|
|
|
|
"""
|
|
|
|
if index is None:
|
|
|
|
return ""
|
|
|
|
return super().id_for_label(id_, index)
|
|
|
|
|
2013-04-13 08:02:28 +08:00
|
|
|
|
2021-05-13 15:26:54 +08:00
|
|
|
class CheckboxSelectMultiple(RadioSelect):
|
2016-12-28 06:00:56 +08:00
|
|
|
allow_multiple_selected = True
|
2013-04-13 08:02:28 +08:00
|
|
|
input_type = "checkbox"
|
2016-12-28 06:00:56 +08:00
|
|
|
template_name = "django/forms/widgets/checkbox_select.html"
|
|
|
|
option_template_name = "django/forms/widgets/checkbox_option.html"
|
2013-04-13 08:02:28 +08:00
|
|
|
|
2016-08-13 01:59:01 +08:00
|
|
|
def use_required_attribute(self, initial):
|
|
|
|
# Don't use the 'required' attribute because browser validation would
|
2016-07-22 08:16:22 +08:00
|
|
|
# require all checkboxes to be checked instead of at least one.
|
2016-08-13 01:59:01 +08:00
|
|
|
return False
|
2016-07-22 08:16:22 +08:00
|
|
|
|
2016-10-01 02:49:50 +08:00
|
|
|
def value_omitted_from_data(self, data, files, name):
|
|
|
|
# HTML checkboxes don't appear in POST data if not checked, so it's
|
|
|
|
# never known if the value is actually omitted.
|
|
|
|
return False
|
|
|
|
|
2007-01-24 04:23:07 +08:00
|
|
|
|
|
|
|
class MultiWidget(Widget):
|
|
|
|
"""
|
|
|
|
A widget that is composed of multiple widgets.
|
|
|
|
|
2016-12-28 06:00:56 +08:00
|
|
|
In addition to the values added by Widget.get_context(), this widget
|
|
|
|
adds a list of subwidgets to the context as widget['subwidgets'].
|
|
|
|
These can be looped over and rendered like normal widgets.
|
2007-01-24 04:23:07 +08:00
|
|
|
|
2007-07-01 12:24:20 +08:00
|
|
|
You'll probably want to use this class with MultiValueField.
|
2007-01-24 04:23:07 +08:00
|
|
|
"""
|
2022-02-04 03:24:19 +08:00
|
|
|
|
2016-12-28 06:00:56 +08:00
|
|
|
template_name = "django/forms/widgets/multiwidget.html"
|
2022-01-14 07:08:38 +08:00
|
|
|
use_fieldset = True
|
2016-12-28 06:00:56 +08:00
|
|
|
|
2007-01-24 04:23:07 +08:00
|
|
|
def __init__(self, widgets, attrs=None):
|
2020-02-14 06:12:46 +08:00
|
|
|
if isinstance(widgets, dict):
|
|
|
|
self.widgets_names = [("_%s" % name) if name else "" for name in widgets]
|
|
|
|
widgets = widgets.values()
|
|
|
|
else:
|
|
|
|
self.widgets_names = ["_%s" % i for i in range(len(widgets))]
|
2013-05-17 22:33:36 +08:00
|
|
|
self.widgets = [w() if isinstance(w, type) else w for w in widgets]
|
2017-01-21 21:13:44 +08:00
|
|
|
super().__init__(attrs)
|
2007-01-24 04:23:07 +08:00
|
|
|
|
2014-02-26 23:03:51 +08:00
|
|
|
@property
|
|
|
|
def is_hidden(self):
|
|
|
|
return all(w.is_hidden for w in self.widgets)
|
|
|
|
|
2017-03-20 21:42:59 +08:00
|
|
|
def get_context(self, name, value, attrs):
|
2017-01-21 21:13:44 +08:00
|
|
|
context = super().get_context(name, value, attrs)
|
2010-05-21 22:07:54 +08:00
|
|
|
if self.is_localized:
|
|
|
|
for widget in self.widgets:
|
|
|
|
widget.is_localized = self.is_localized
|
2022-04-22 14:51:39 +08:00
|
|
|
# value is a list/tuple of values, each corresponding to a widget
|
2007-01-24 04:23:07 +08:00
|
|
|
# in self.widgets.
|
2022-04-22 14:51:39 +08:00
|
|
|
if not isinstance(value, (list, tuple)):
|
2007-01-24 04:23:07 +08:00
|
|
|
value = self.decompress(value)
|
2016-12-28 06:00:56 +08:00
|
|
|
|
|
|
|
final_attrs = context["widget"]["attrs"]
|
2017-01-13 19:34:33 +08:00
|
|
|
input_type = final_attrs.pop("type", None)
|
2015-05-14 02:51:18 +08:00
|
|
|
id_ = final_attrs.get("id")
|
2016-12-28 06:00:56 +08:00
|
|
|
subwidgets = []
|
2020-02-14 06:12:46 +08:00
|
|
|
for i, (widget_name, widget) in enumerate(
|
|
|
|
zip(self.widgets_names, self.widgets)
|
|
|
|
):
|
2017-01-13 19:34:33 +08:00
|
|
|
if input_type is not None:
|
|
|
|
widget.input_type = input_type
|
2020-02-14 06:12:46 +08:00
|
|
|
widget_name = name + widget_name
|
2007-01-24 04:23:07 +08:00
|
|
|
try:
|
|
|
|
widget_value = value[i]
|
2007-04-24 20:53:29 +08:00
|
|
|
except IndexError:
|
2007-01-24 04:23:07 +08:00
|
|
|
widget_value = None
|
2007-04-24 20:53:29 +08:00
|
|
|
if id_:
|
2016-12-28 06:00:56 +08:00
|
|
|
widget_attrs = final_attrs.copy()
|
|
|
|
widget_attrs["id"] = "%s_%s" % (id_, i)
|
|
|
|
else:
|
|
|
|
widget_attrs = final_attrs
|
|
|
|
subwidgets.append(
|
|
|
|
widget.get_context(widget_name, widget_value, widget_attrs)["widget"]
|
|
|
|
)
|
|
|
|
context["widget"]["subwidgets"] = subwidgets
|
|
|
|
return context
|
2007-01-24 04:23:07 +08:00
|
|
|
|
2007-04-24 20:53:29 +08:00
|
|
|
def id_for_label(self, id_):
|
2021-11-05 16:11:25 +08:00
|
|
|
return ""
|
2007-04-24 20:53:29 +08:00
|
|
|
|
2007-08-06 21:58:56 +08:00
|
|
|
def value_from_datadict(self, data, files, name):
|
2020-02-14 06:12:46 +08:00
|
|
|
return [
|
|
|
|
widget.value_from_datadict(data, files, name + widget_name)
|
|
|
|
for widget_name, widget in zip(self.widgets_names, self.widgets)
|
|
|
|
]
|
2008-08-25 08:32:32 +08:00
|
|
|
|
2016-09-07 05:41:54 +08:00
|
|
|
def value_omitted_from_data(self, data, files, name):
|
|
|
|
return all(
|
2020-02-14 06:12:46 +08:00
|
|
|
widget.value_omitted_from_data(data, files, name + widget_name)
|
|
|
|
for widget_name, widget in zip(self.widgets_names, self.widgets)
|
2016-09-07 05:41:54 +08:00
|
|
|
)
|
|
|
|
|
2007-01-24 04:23:07 +08:00
|
|
|
def decompress(self, value):
|
|
|
|
"""
|
2017-01-25 05:23:56 +08:00
|
|
|
Return a list of decompressed values for the given compressed value.
|
2007-01-24 04:23:07 +08:00
|
|
|
The given value can be assumed to be valid, but not necessarily
|
|
|
|
non-empty.
|
|
|
|
"""
|
|
|
|
raise NotImplementedError("Subclasses must implement this method.")
|
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
def _get_media(self):
|
2017-01-25 05:23:56 +08:00
|
|
|
"""
|
|
|
|
Media for a multiwidget is the combination of all media of the
|
|
|
|
subwidgets.
|
|
|
|
"""
|
2008-07-19 07:54:34 +08:00
|
|
|
media = Media()
|
|
|
|
for w in self.widgets:
|
|
|
|
media = media + w.media
|
|
|
|
return media
|
2022-02-04 03:24:19 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
media = property(_get_media)
|
2010-04-28 11:02:53 +08:00
|
|
|
|
2010-03-10 06:52:04 +08:00
|
|
|
def __deepcopy__(self, memo):
|
2017-01-21 21:13:44 +08:00
|
|
|
obj = super().__deepcopy__(memo)
|
2010-03-10 06:52:04 +08:00
|
|
|
obj.widgets = copy.deepcopy(self.widgets)
|
|
|
|
return obj
|
2008-08-25 08:32:32 +08:00
|
|
|
|
2013-08-05 22:01:49 +08:00
|
|
|
@property
|
|
|
|
def needs_multipart_form(self):
|
|
|
|
return any(w.needs_multipart_form for w in self.widgets)
|
|
|
|
|
|
|
|
|
2007-01-24 04:23:07 +08:00
|
|
|
class SplitDateTimeWidget(MultiWidget):
|
|
|
|
"""
|
2017-01-25 05:23:56 +08:00
|
|
|
A widget that splits datetime input into two <input type="text"> boxes.
|
2007-01-24 04:23:07 +08:00
|
|
|
"""
|
2009-03-23 00:13:06 +08:00
|
|
|
|
2017-01-13 19:48:30 +08:00
|
|
|
supports_microseconds = False
|
|
|
|
template_name = "django/forms/widgets/splitdatetime.html"
|
2022-02-04 03:24:19 +08:00
|
|
|
|
2017-01-13 19:48:30 +08:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
attrs=None,
|
|
|
|
date_format=None,
|
|
|
|
time_format=None,
|
|
|
|
date_attrs=None,
|
|
|
|
time_attrs=None,
|
|
|
|
):
|
2016-03-29 06:33:29 +08:00
|
|
|
widgets = (
|
2017-01-13 19:48:30 +08:00
|
|
|
DateInput(
|
|
|
|
attrs=attrs if date_attrs is None else date_attrs,
|
|
|
|
format=date_format,
|
|
|
|
),
|
|
|
|
TimeInput(
|
|
|
|
attrs=attrs if time_attrs is None else time_attrs,
|
|
|
|
format=time_format,
|
|
|
|
),
|
2016-03-29 06:33:29 +08:00
|
|
|
)
|
2017-01-13 19:48:30 +08:00
|
|
|
super().__init__(widgets)
|
2007-01-24 04:23:07 +08:00
|
|
|
|
|
|
|
def decompress(self, value):
|
|
|
|
if value:
|
2011-11-18 21:01:06 +08:00
|
|
|
value = to_current_timezone(value)
|
2018-01-19 00:23:06 +08:00
|
|
|
return [value.date(), value.time()]
|
2007-01-24 04:23:07 +08:00
|
|
|
return [None, None]
|
2007-11-14 20:58:53 +08:00
|
|
|
|
2013-11-03 05:34:05 +08:00
|
|
|
|
2008-09-02 05:28:32 +08:00
|
|
|
class SplitHiddenDateTimeWidget(SplitDateTimeWidget):
|
|
|
|
"""
|
2017-01-25 05:23:56 +08:00
|
|
|
A widget that splits datetime input into two <input type="hidden"> inputs.
|
2008-09-02 05:28:32 +08:00
|
|
|
"""
|
2016-12-28 06:00:56 +08:00
|
|
|
|
2017-01-13 19:48:30 +08:00
|
|
|
template_name = "django/forms/widgets/splithiddendatetime.html"
|
2022-02-04 03:24:19 +08:00
|
|
|
|
2017-01-13 19:48:30 +08:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
attrs=None,
|
|
|
|
date_format=None,
|
|
|
|
time_format=None,
|
|
|
|
date_attrs=None,
|
|
|
|
time_attrs=None,
|
|
|
|
):
|
|
|
|
super().__init__(attrs, date_format, time_format, date_attrs, time_attrs)
|
2010-03-28 00:43:27 +08:00
|
|
|
for widget in self.widgets:
|
|
|
|
widget.input_type = "hidden"
|
2015-01-26 11:28:57 +08:00
|
|
|
|
|
|
|
|
|
|
|
class SelectDateWidget(Widget):
|
|
|
|
"""
|
2017-01-25 05:23:56 +08:00
|
|
|
A widget that splits date input into three <select> boxes.
|
2015-01-26 11:28:57 +08:00
|
|
|
|
|
|
|
This also serves as an example of a Widget that has more than one HTML
|
|
|
|
element and hence implements value_from_datadict.
|
|
|
|
"""
|
2022-02-04 03:24:19 +08:00
|
|
|
|
2018-01-30 17:33:13 +08:00
|
|
|
none_value = ("", "---")
|
2015-01-26 11:28:57 +08:00
|
|
|
month_field = "%s_month"
|
|
|
|
day_field = "%s_day"
|
|
|
|
year_field = "%s_year"
|
2016-12-28 06:00:56 +08:00
|
|
|
template_name = "django/forms/widgets/select_date.html"
|
|
|
|
input_type = "select"
|
2015-07-15 10:57:23 +08:00
|
|
|
select_widget = Select
|
2019-10-26 22:42:32 +08:00
|
|
|
date_re = _lazy_re_compile(r"(\d{4}|0)-(\d\d?)-(\d\d?)$")
|
2022-01-14 07:08:38 +08:00
|
|
|
use_fieldset = True
|
2015-01-26 11:28:57 +08:00
|
|
|
|
|
|
|
def __init__(self, attrs=None, years=None, months=None, empty_label=None):
|
|
|
|
self.attrs = attrs or {}
|
|
|
|
|
|
|
|
# Optional list or tuple of years to use in the "year" select box.
|
|
|
|
if years:
|
|
|
|
self.years = years
|
|
|
|
else:
|
|
|
|
this_year = datetime.date.today().year
|
|
|
|
self.years = range(this_year, this_year + 10)
|
|
|
|
|
|
|
|
# Optional dict of months to use in the "month" select box.
|
|
|
|
if months:
|
|
|
|
self.months = months
|
|
|
|
else:
|
|
|
|
self.months = MONTHS
|
|
|
|
|
|
|
|
# Optional string, list, or tuple to use as empty_label.
|
|
|
|
if isinstance(empty_label, (list, tuple)):
|
|
|
|
if not len(empty_label) == 3:
|
|
|
|
raise ValueError("empty_label list/tuple must have 3 elements.")
|
|
|
|
|
2018-01-30 17:33:13 +08:00
|
|
|
self.year_none_value = ("", empty_label[0])
|
|
|
|
self.month_none_value = ("", empty_label[1])
|
|
|
|
self.day_none_value = ("", empty_label[2])
|
2015-01-26 11:28:57 +08:00
|
|
|
else:
|
|
|
|
if empty_label is not None:
|
2018-01-30 17:33:13 +08:00
|
|
|
self.none_value = ("", empty_label)
|
2015-01-26 11:28:57 +08:00
|
|
|
|
|
|
|
self.year_none_value = self.none_value
|
|
|
|
self.month_none_value = self.none_value
|
|
|
|
self.day_none_value = self.none_value
|
|
|
|
|
2017-03-20 21:42:59 +08:00
|
|
|
def get_context(self, name, value, attrs):
|
2017-01-21 21:13:44 +08:00
|
|
|
context = super().get_context(name, value, attrs)
|
2016-12-28 06:00:56 +08:00
|
|
|
date_context = {}
|
2017-08-30 02:56:08 +08:00
|
|
|
year_choices = [(i, str(i)) for i in self.years]
|
2017-04-22 00:34:18 +08:00
|
|
|
if not self.is_required:
|
2016-12-28 06:00:56 +08:00
|
|
|
year_choices.insert(0, self.year_none_value)
|
|
|
|
year_name = self.year_field % name
|
|
|
|
date_context["year"] = self.select_widget(
|
|
|
|
attrs, choices=year_choices
|
|
|
|
).get_context(
|
|
|
|
name=year_name,
|
|
|
|
value=context["widget"]["value"]["year"],
|
2019-11-22 03:53:31 +08:00
|
|
|
attrs={**context["widget"]["attrs"], "id": "id_%s" % year_name},
|
2016-12-28 06:00:56 +08:00
|
|
|
)
|
|
|
|
month_choices = list(self.months.items())
|
2017-04-22 00:34:18 +08:00
|
|
|
if not self.is_required:
|
2016-12-28 06:00:56 +08:00
|
|
|
month_choices.insert(0, self.month_none_value)
|
|
|
|
month_name = self.month_field % name
|
|
|
|
date_context["month"] = self.select_widget(
|
|
|
|
attrs, choices=month_choices
|
|
|
|
).get_context(
|
|
|
|
name=month_name,
|
|
|
|
value=context["widget"]["value"]["month"],
|
2019-11-22 03:53:31 +08:00
|
|
|
attrs={**context["widget"]["attrs"], "id": "id_%s" % month_name},
|
2016-12-28 06:00:56 +08:00
|
|
|
)
|
|
|
|
day_choices = [(i, i) for i in range(1, 32)]
|
2017-04-22 00:34:18 +08:00
|
|
|
if not self.is_required:
|
2016-12-28 06:00:56 +08:00
|
|
|
day_choices.insert(0, self.day_none_value)
|
|
|
|
day_name = self.day_field % name
|
|
|
|
date_context["day"] = self.select_widget(
|
|
|
|
attrs,
|
|
|
|
choices=day_choices,
|
|
|
|
).get_context(
|
|
|
|
name=day_name,
|
|
|
|
value=context["widget"]["value"]["day"],
|
2019-11-22 03:53:31 +08:00
|
|
|
attrs={**context["widget"]["attrs"], "id": "id_%s" % day_name},
|
2016-12-28 06:00:56 +08:00
|
|
|
)
|
|
|
|
subwidgets = []
|
|
|
|
for field in self._parse_date_fmt():
|
|
|
|
subwidgets.append(date_context[field]["widget"])
|
|
|
|
context["widget"]["subwidgets"] = subwidgets
|
|
|
|
return context
|
|
|
|
|
|
|
|
def format_value(self, value):
|
|
|
|
"""
|
|
|
|
Return a dict containing the year, month, and day of the current value.
|
|
|
|
Use dict instead of a datetime to allow invalid dates such as February
|
|
|
|
31 to display correctly.
|
|
|
|
"""
|
|
|
|
year, month, day = None, None, None
|
|
|
|
if isinstance(value, (datetime.date, datetime.datetime)):
|
|
|
|
year, month, day = value.year, value.month, value.day
|
2016-12-29 23:27:49 +08:00
|
|
|
elif isinstance(value, str):
|
2018-01-31 07:11:05 +08:00
|
|
|
match = self.date_re.match(value)
|
|
|
|
if match:
|
2018-01-30 17:33:13 +08:00
|
|
|
# Convert any zeros in the date to empty strings to match the
|
|
|
|
# empty option value.
|
|
|
|
year, month, day = [int(val) or "" for val in match.groups()]
|
2020-07-10 18:57:39 +08:00
|
|
|
else:
|
2017-03-09 23:17:41 +08:00
|
|
|
input_format = get_format("DATE_INPUT_FORMATS")[0]
|
2016-12-28 06:00:56 +08:00
|
|
|
try:
|
2017-01-12 06:17:25 +08:00
|
|
|
d = datetime.datetime.strptime(value, input_format)
|
2016-12-28 06:00:56 +08:00
|
|
|
except ValueError:
|
|
|
|
pass
|
2017-03-09 23:17:41 +08:00
|
|
|
else:
|
|
|
|
year, month, day = d.year, d.month, d.day
|
2016-12-28 06:00:56 +08:00
|
|
|
return {"year": year, "month": month, "day": day}
|
|
|
|
|
2015-01-26 11:28:57 +08:00
|
|
|
@staticmethod
|
|
|
|
def _parse_date_fmt():
|
|
|
|
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"
|
|
|
|
|
|
|
|
def id_for_label(self, id_):
|
|
|
|
for first_select in self._parse_date_fmt():
|
|
|
|
return "%s_%s" % (id_, first_select)
|
2017-08-08 10:01:45 +08:00
|
|
|
return "%s_month" % id_
|
2015-01-26 11:28:57 +08:00
|
|
|
|
|
|
|
def value_from_datadict(self, data, files, name):
|
|
|
|
y = data.get(self.year_field % name)
|
|
|
|
m = data.get(self.month_field % name)
|
|
|
|
d = data.get(self.day_field % name)
|
2018-01-30 17:33:13 +08:00
|
|
|
if y == m == d == "":
|
2015-01-26 11:28:57 +08:00
|
|
|
return None
|
2018-01-30 17:33:13 +08:00
|
|
|
if y is not None and m is not None and d is not None:
|
2019-06-21 03:46:58 +08:00
|
|
|
input_format = get_format("DATE_INPUT_FORMATS")[0]
|
2021-05-10 21:39:50 +08:00
|
|
|
input_format = formats.sanitize_strftime_format(input_format)
|
2019-06-21 03:46:58 +08:00
|
|
|
try:
|
|
|
|
date_value = datetime.date(int(y), int(m), int(d))
|
|
|
|
except ValueError:
|
|
|
|
# Return pseudo-ISO dates with zeros for any unselected values,
|
|
|
|
# e.g. '2017-0-23'.
|
|
|
|
return "%s-%s-%s" % (y or 0, m or 0, d or 0)
|
|
|
|
return date_value.strftime(input_format)
|
2015-05-14 02:51:18 +08:00
|
|
|
return data.get(name)
|
2015-01-26 11:28:57 +08:00
|
|
|
|
2016-09-07 05:41:54 +08:00
|
|
|
def value_omitted_from_data(self, data, files, name):
|
|
|
|
return not any(
|
|
|
|
("{}_{}".format(name, interval) in data)
|
|
|
|
for interval in ("year", "month", "day")
|
|
|
|
)
|