diff options
Diffstat (limited to 'django/forms/widgets.py')
| -rw-r--r-- | django/forms/widgets.py | 514 |
1 files changed, 299 insertions, 215 deletions
diff --git a/django/forms/widgets.py b/django/forms/widgets.py index 05667f8e44..8c5122ad1d 100644 --- a/django/forms/widgets.py +++ b/django/forms/widgets.py @@ -17,24 +17,41 @@ from django.utils.formats import get_format from django.utils.html import format_html, html_safe from django.utils.regex_helper import _lazy_re_compile from django.utils.safestring import mark_safe -from django.utils.topological_sort import ( - CyclicDependencyError, stable_topological_sort, -) +from django.utils.topological_sort import CyclicDependencyError, stable_topological_sort from django.utils.translation import gettext_lazy as _ from .renderers import get_default_renderer __all__ = ( - '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', + "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", ) -MEDIA_TYPES = ('css', 'js') +MEDIA_TYPES = ("css", "js") class MediaOrderConflictWarning(RuntimeWarning): @@ -45,8 +62,8 @@ class MediaOrderConflictWarning(RuntimeWarning): class Media: def __init__(self, media=None, css=None, js=None): if media is not None: - css = getattr(media, 'css', {}) - js = getattr(media, 'js', []) + css = getattr(media, "css", {}) + js = getattr(media, "js", []) else: if css is None: css = {} @@ -56,7 +73,7 @@ class Media: self._js_lists = [js] def __repr__(self): - return 'Media(css=%r, js=%r)' % (self._css, self._js) + return "Media(css=%r, js=%r)" % (self._css, self._js) def __str__(self): return self.render() @@ -74,26 +91,35 @@ class Media: return self.merge(*self._js_lists) def render(self): - return mark_safe('\n'.join(chain.from_iterable(getattr(self, 'render_' + name)() for name in MEDIA_TYPES))) + return mark_safe( + "\n".join( + chain.from_iterable( + getattr(self, "render_" + name)() for name in MEDIA_TYPES + ) + ) + ) def render_js(self): return [ - format_html( - '<script src="{}"></script>', - self.absolute_path(path) - ) for path in self._js + format_html('<script src="{}"></script>', self.absolute_path(path)) + for path in self._js ] 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. media = sorted(self._css) - return chain.from_iterable([ - format_html( - '<link href="{}" media="{}" rel="stylesheet">', - self.absolute_path(path), medium - ) for path in self._css[medium] - ] for medium in media) + return chain.from_iterable( + [ + format_html( + '<link href="{}" media="{}" rel="stylesheet">', + self.absolute_path(path), + medium, + ) + for path in self._css[medium] + ] + for medium in media + ) def absolute_path(self, path): """ @@ -101,14 +127,14 @@ class Media: path. An absolute path will be returned unchanged while a relative path will be passed to django.templatetags.static.static(). """ - if path.startswith(('http://', 'https://', '/')): + if path.startswith(("http://", "https://", "/")): return path return static(path) def __getitem__(self, name): """Return a Media object that only contains media of the given type.""" if name in MEDIA_TYPES: - return Media(**{str(name): getattr(self, '_' + name)}) + return Media(**{str(name): getattr(self, "_" + name)}) raise KeyError('Unknown media type "%s"' % name) @staticmethod @@ -138,9 +164,10 @@ class Media: return stable_topological_sort(all_items, dependency_graph) except CyclicDependencyError: warnings.warn( - 'Detected duplicate Media files in an opposite order: {}'.format( - ', '.join(repr(list_) for list_ in lists) - ), MediaOrderConflictWarning, + "Detected duplicate Media files in an opposite order: {}".format( + ", ".join(repr(list_) for list_ in lists) + ), + MediaOrderConflictWarning, ) return list(all_items) @@ -167,9 +194,9 @@ def media_property(cls): base = Media() # Get the media definition for this class - definition = getattr(cls, 'Media', None) + definition = getattr(cls, "Media", None) if definition: - extend = getattr(definition, 'extend', True) + extend = getattr(definition, "extend", True) if extend: if extend is True: m = base @@ -180,6 +207,7 @@ def media_property(cls): return m + Media(definition) return Media(definition) return base + return property(_media) @@ -187,10 +215,11 @@ class MediaDefiningClass(type): """ Metaclass for classes that can have media definitions. """ + def __new__(mcs, name, bases, attrs): new_class = super().__new__(mcs, name, bases, attrs) - if 'media' not in attrs: + if "media" not in attrs: new_class.media = media_property(new_class) return new_class @@ -213,17 +242,17 @@ class Widget(metaclass=MediaDefiningClass): @property def is_hidden(self): - return self.input_type == 'hidden' if hasattr(self, 'input_type') else False + return self.input_type == "hidden" if hasattr(self, "input_type") else False def subwidgets(self, name, value, attrs=None): context = self.get_context(name, value, attrs) - yield context['widget'] + yield context["widget"] def format_value(self, value): """ Return a value as it should appear when rendered in a template. """ - if value == '' or value is None: + if value == "" or value is None: return None if self.is_localized: return formats.localize_input(value) @@ -231,13 +260,13 @@ class Widget(metaclass=MediaDefiningClass): def get_context(self, name, value, attrs): 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, + "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, }, } @@ -285,44 +314,45 @@ class Input(Widget): """ Base class for all <input> widgets. """ + input_type = None # Subclasses must define this. - template_name = 'django/forms/widgets/input.html' + template_name = "django/forms/widgets/input.html" def __init__(self, attrs=None): if attrs is not None: attrs = attrs.copy() - self.input_type = attrs.pop('type', self.input_type) + self.input_type = attrs.pop("type", self.input_type) super().__init__(attrs) def get_context(self, name, value, attrs): context = super().get_context(name, value, attrs) - context['widget']['type'] = self.input_type + context["widget"]["type"] = self.input_type return context class TextInput(Input): - input_type = 'text' - template_name = 'django/forms/widgets/text.html' + input_type = "text" + template_name = "django/forms/widgets/text.html" class NumberInput(Input): - input_type = 'number' - template_name = 'django/forms/widgets/number.html' + input_type = "number" + template_name = "django/forms/widgets/number.html" class EmailInput(Input): - input_type = 'email' - template_name = 'django/forms/widgets/email.html' + input_type = "email" + template_name = "django/forms/widgets/email.html" class URLInput(Input): - input_type = 'url' - template_name = 'django/forms/widgets/url.html' + input_type = "url" + template_name = "django/forms/widgets/url.html" class PasswordInput(Input): - input_type = 'password' - template_name = 'django/forms/widgets/password.html' + input_type = "password" + template_name = "django/forms/widgets/password.html" def __init__(self, attrs=None, render_value=False): super().__init__(attrs) @@ -335,8 +365,8 @@ class PasswordInput(Input): class HiddenInput(Input): - input_type = 'hidden' - template_name = 'django/forms/widgets/hidden.html' + input_type = "hidden" + template_name = "django/forms/widgets/hidden.html" class MultipleHiddenInput(HiddenInput): @@ -344,25 +374,26 @@ class MultipleHiddenInput(HiddenInput): Handle <input type="hidden"> for fields that have a list of values. """ - template_name = 'django/forms/widgets/multiple_hidden.html' + + template_name = "django/forms/widgets/multiple_hidden.html" def get_context(self, name, value, attrs): context = super().get_context(name, value, attrs) - final_attrs = context['widget']['attrs'] - id_ = context['widget']['attrs'].get('id') + final_attrs = context["widget"]["attrs"] + id_ = context["widget"]["attrs"].get("id") subwidgets = [] - for index, value_ in enumerate(context['widget']['value']): + for index, value_ in enumerate(context["widget"]["value"]): widget_attrs = final_attrs.copy() 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. - widget_attrs['id'] = '%s_%s' % (id_, index) + 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']) + subwidgets.append(widget.get_context(name, value_, widget_attrs)["widget"]) - context['widget']['subwidgets'] = subwidgets + context["widget"]["subwidgets"] = subwidgets return context def value_from_datadict(self, data, files, name): @@ -377,9 +408,9 @@ class MultipleHiddenInput(HiddenInput): class FileInput(Input): - input_type = 'file' + input_type = "file" needs_multipart_form = True - template_name = 'django/forms/widgets/file.html' + template_name = "django/forms/widgets/file.html" def format_value(self, value): """File input never renders a value.""" @@ -400,29 +431,29 @@ FILE_INPUT_CONTRADICTION = object() class ClearableFileInput(FileInput): - clear_checkbox_label = _('Clear') - initial_text = _('Currently') - input_text = _('Change') - template_name = 'django/forms/widgets/clearable_file_input.html' + clear_checkbox_label = _("Clear") + initial_text = _("Currently") + input_text = _("Change") + template_name = "django/forms/widgets/clearable_file_input.html" def clear_checkbox_name(self, name): """ Given the name of the file input, return the name of the clear checkbox input. """ - return name + '-clear' + 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' + return name + "_id" def is_initial(self, value): """ Return whether value is considered to be initial value. """ - return bool(value and getattr(value, 'url', False)) + return bool(value and getattr(value, "url", False)) def format_value(self, value): """ @@ -435,20 +466,23 @@ class ClearableFileInput(FileInput): context = super().get_context(name, value, attrs) checkbox_name = self.clear_checkbox_name(name) checkbox_id = self.clear_checkbox_id(checkbox_name) - context['widget'].update({ - 'checkbox_name': checkbox_name, - 'checkbox_id': checkbox_id, - 'is_initial': self.is_initial(value), - 'input_text': self.input_text, - 'initial_text': self.initial_text, - 'clear_checkbox_label': self.clear_checkbox_label, - }) + context["widget"].update( + { + "checkbox_name": checkbox_name, + "checkbox_id": checkbox_id, + "is_initial": self.is_initial(value), + "input_text": self.input_text, + "initial_text": self.initial_text, + "clear_checkbox_label": self.clear_checkbox_label, + } + ) return context def value_from_datadict(self, data, files, name): upload = super().value_from_datadict(data, files, name) if not self.is_required and CheckboxInput().value_from_datadict( - data, files, self.clear_checkbox_name(name)): + data, files, self.clear_checkbox_name(name) + ): if upload: # If the user contradicts themselves (uploads a new file AND @@ -461,24 +495,24 @@ class ClearableFileInput(FileInput): 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 + super().value_omitted_from_data(data, files, name) + and self.clear_checkbox_name(name) not in data ) class Textarea(Widget): - template_name = 'django/forms/widgets/textarea.html' + template_name = "django/forms/widgets/textarea.html" def __init__(self, attrs=None): # Use slightly better defaults than HTML's 20x2 box - default_attrs = {'cols': '40', 'rows': '10'} + default_attrs = {"cols": "40", "rows": "10"} if attrs: default_attrs.update(attrs) super().__init__(default_attrs) class DateTimeBaseInput(TextInput): - format_key = '' + format_key = "" supports_microseconds = False def __init__(self, attrs=None, format=None): @@ -486,32 +520,34 @@ class DateTimeBaseInput(TextInput): self.format = format or None def format_value(self, value): - return formats.localize_input(value, self.format or formats.get_format(self.format_key)[0]) + return formats.localize_input( + value, self.format or formats.get_format(self.format_key)[0] + ) class DateInput(DateTimeBaseInput): - format_key = 'DATE_INPUT_FORMATS' - template_name = 'django/forms/widgets/date.html' + format_key = "DATE_INPUT_FORMATS" + template_name = "django/forms/widgets/date.html" class DateTimeInput(DateTimeBaseInput): - format_key = 'DATETIME_INPUT_FORMATS' - template_name = 'django/forms/widgets/datetime.html' + format_key = "DATETIME_INPUT_FORMATS" + template_name = "django/forms/widgets/datetime.html" class TimeInput(DateTimeBaseInput): - format_key = 'TIME_INPUT_FORMATS' - template_name = 'django/forms/widgets/time.html' + format_key = "TIME_INPUT_FORMATS" + template_name = "django/forms/widgets/time.html" # 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 == '') + return not (v is False or v is None or v == "") class CheckboxInput(Input): - input_type = 'checkbox' - template_name = 'django/forms/widgets/checkbox.html' + input_type = "checkbox" + template_name = "django/forms/widgets/checkbox.html" def __init__(self, attrs=None, check_test=None): super().__init__(attrs) @@ -521,13 +557,13 @@ class CheckboxInput(Input): 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 == '': + if value is True or value is False or value is None or value == "": return return str(value) def get_context(self, name, value, attrs): if self.check_test(value): - attrs = {**(attrs or {}), 'checked': True} + attrs = {**(attrs or {}), "checked": True} return super().get_context(name, value, attrs) def value_from_datadict(self, data, files, name): @@ -537,7 +573,7 @@ class CheckboxInput(Input): return False value = data.get(name) # Translate true and false strings to boolean values. - values = {'true': True, 'false': False} + values = {"true": True, "false": False} if isinstance(value, str): value = values.get(value.lower(), value) return bool(value) @@ -554,7 +590,7 @@ class ChoiceWidget(Widget): template_name = None option_template_name = None add_id_index = True - checked_attribute = {'checked': True} + checked_attribute = {"checked": True} option_inherits_attrs = True def __init__(self, attrs=None, choices=()): @@ -591,7 +627,7 @@ class ChoiceWidget(Widget): for index, (option_value, option_label) in enumerate(self.choices): if option_value is None: - option_value = '' + option_value = "" subgroup = [] if isinstance(option_label, (list, tuple)): @@ -605,50 +641,62 @@ class ChoiceWidget(Widget): groups.append((group_name, subgroup, index)) for subvalue, sublabel in choices: - selected = ( - (not has_selected or self.allow_multiple_selected) and - str(subvalue) in value - ) + selected = (not has_selected or self.allow_multiple_selected) and str( + subvalue + ) in value has_selected |= selected - subgroup.append(self.create_option( - name, subvalue, sublabel, selected, index, - subindex=subindex, attrs=attrs, - )) + subgroup.append( + self.create_option( + name, + subvalue, + sublabel, + selected, + index, + subindex=subindex, + attrs=attrs, + ) + ) if subindex is not None: subindex += 1 return groups - def create_option(self, name, value, label, selected, index, subindex=None, attrs=None): + 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 {} + option_attrs = ( + self.build_attrs(self.attrs, attrs) if self.option_inherits_attrs else {} + ) if selected: option_attrs.update(self.checked_attribute) - if 'id' in option_attrs: - option_attrs['id'] = self.id_for_label(option_attrs['id'], index) + if "id" in option_attrs: + option_attrs["id"] = self.id_for_label(option_attrs["id"], index) return { - 'name': name, - 'value': value, - 'label': label, - 'selected': selected, - 'index': index, - 'attrs': option_attrs, - 'type': self.input_type, - 'template_name': self.option_template_name, - 'wrap_label': True, + "name": name, + "value": value, + "label": label, + "selected": selected, + "index": index, + "attrs": option_attrs, + "type": self.input_type, + "template_name": self.option_template_name, + "wrap_label": True, } def get_context(self, name, value, attrs): context = super().get_context(name, value, attrs) - context['widget']['optgroups'] = self.optgroups(name, context['widget']['value'], attrs) + context["widget"]["optgroups"] = self.optgroups( + name, context["widget"]["value"], attrs + ) return context - def id_for_label(self, id_, index='0'): + 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) + id_ = "%s_%s" % (id_, index) return id_ def value_from_datadict(self, data, files, name): @@ -666,28 +714,28 @@ class ChoiceWidget(Widget): return [] if not isinstance(value, (tuple, list)): value = [value] - return [str(v) if v is not None else '' for v in value] + return [str(v) if v is not None else "" for v in value] class Select(ChoiceWidget): - input_type = 'select' - template_name = 'django/forms/widgets/select.html' - option_template_name = 'django/forms/widgets/select_option.html' + 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} + checked_attribute = {"selected": True} option_inherits_attrs = False def get_context(self, name, value, attrs): context = super().get_context(name, value, attrs) if self.allow_multiple_selected: - context['widget']['attrs']['multiple'] = True + context["widget"]["attrs"]["multiple"] = True return context @staticmethod def _choice_has_empty_value(choice): """Return True if the choice's value is empty string or None.""" value, _ = choice - return value is None or value == '' + return value is None or value == "" def use_required_attribute(self, initial): """ @@ -700,44 +748,52 @@ class Select(ChoiceWidget): 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) + return ( + use_required_attribute + and first_choice is not None + and self._choice_has_empty_value(first_choice) + ) class NullBooleanSelect(Select): """ A Select Widget intended to be used with NullBooleanField. """ + def __init__(self, attrs=None): choices = ( - ('unknown', _('Unknown')), - ('true', _('Yes')), - ('false', _('No')), + ("unknown", _("Unknown")), + ("true", _("Yes")), + ("false", _("No")), ) super().__init__(attrs, choices) def format_value(self, value): try: return { - True: 'true', False: 'false', - 'true': 'true', 'false': 'false', + True: "true", + False: "false", + "true": "true", + "false": "false", # For backwards compatibility with Django < 2.2. - '2': 'true', '3': 'false', + "2": "true", + "3": "false", }[value] except KeyError: - return 'unknown' + return "unknown" def value_from_datadict(self, data, files, name): value = data.get(name) return { True: True, - 'True': True, - 'False': False, + "True": True, + "False": False, False: False, - 'true': True, - 'false': False, + "true": True, + "false": False, # For backwards compatibility with Django < 2.2. - '2': True, - '3': False, + "2": True, + "3": False, }.get(value) @@ -758,9 +814,9 @@ class SelectMultiple(Select): class RadioSelect(ChoiceWidget): - input_type = 'radio' - template_name = 'django/forms/widgets/radio.html' - option_template_name = 'django/forms/widgets/radio_option.html' + input_type = "radio" + template_name = "django/forms/widgets/radio.html" + option_template_name = "django/forms/widgets/radio_option.html" def id_for_label(self, id_, index=None): """ @@ -769,15 +825,15 @@ class RadioSelect(ChoiceWidget): the first input. """ if index is None: - return '' + return "" return super().id_for_label(id_, index) class CheckboxSelectMultiple(RadioSelect): allow_multiple_selected = True - input_type = 'checkbox' - template_name = 'django/forms/widgets/checkbox_select.html' - option_template_name = 'django/forms/widgets/checkbox_option.html' + input_type = "checkbox" + template_name = "django/forms/widgets/checkbox_select.html" + option_template_name = "django/forms/widgets/checkbox_option.html" def use_required_attribute(self, initial): # Don't use the 'required' attribute because browser validation would @@ -800,16 +856,15 @@ class MultiWidget(Widget): You'll probably want to use this class with MultiValueField. """ - template_name = 'django/forms/widgets/multiwidget.html' + + template_name = "django/forms/widgets/multiwidget.html" def __init__(self, widgets, attrs=None): if isinstance(widgets, dict): - self.widgets_names = [ - ('_%s' % name) if name else '' for name in widgets - ] + 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))] + self.widgets_names = ["_%s" % i for i in range(len(widgets))] self.widgets = [w() if isinstance(w, type) else w for w in widgets] super().__init__(attrs) @@ -827,11 +882,13 @@ class MultiWidget(Widget): if not isinstance(value, list): value = self.decompress(value) - final_attrs = context['widget']['attrs'] - input_type = final_attrs.pop('type', None) - id_ = final_attrs.get('id') + final_attrs = context["widget"]["attrs"] + input_type = final_attrs.pop("type", None) + id_ = final_attrs.get("id") subwidgets = [] - for i, (widget_name, widget) in enumerate(zip(self.widgets_names, self.widgets)): + for i, (widget_name, widget) in enumerate( + zip(self.widgets_names, self.widgets) + ): if input_type is not None: widget.input_type = input_type widget_name = name + widget_name @@ -841,15 +898,17 @@ class MultiWidget(Widget): widget_value = None if id_: widget_attrs = final_attrs.copy() - widget_attrs['id'] = '%s_%s' % (id_, i) + 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 + subwidgets.append( + widget.get_context(widget_name, widget_value, widget_attrs)["widget"] + ) + context["widget"]["subwidgets"] = subwidgets return context def id_for_label(self, id_): - return '' + return "" def value_from_datadict(self, data, files, name): return [ @@ -869,7 +928,7 @@ class MultiWidget(Widget): The given value can be assumed to be valid, but not necessarily non-empty. """ - raise NotImplementedError('Subclasses must implement this method.') + raise NotImplementedError("Subclasses must implement this method.") def _get_media(self): """ @@ -880,6 +939,7 @@ class MultiWidget(Widget): for w in self.widgets: media = media + w.media return media + media = property(_get_media) def __deepcopy__(self, memo): @@ -896,10 +956,18 @@ class SplitDateTimeWidget(MultiWidget): """ A widget that splits datetime input into two <input type="text"> boxes. """ + supports_microseconds = False - template_name = 'django/forms/widgets/splitdatetime.html' + template_name = "django/forms/widgets/splitdatetime.html" - def __init__(self, attrs=None, date_format=None, time_format=None, date_attrs=None, time_attrs=None): + def __init__( + self, + attrs=None, + date_format=None, + time_format=None, + date_attrs=None, + time_attrs=None, + ): widgets = ( DateInput( attrs=attrs if date_attrs is None else date_attrs, @@ -923,12 +991,20 @@ class SplitHiddenDateTimeWidget(SplitDateTimeWidget): """ A widget that splits datetime input into two <input type="hidden"> inputs. """ - template_name = 'django/forms/widgets/splithiddendatetime.html' - def __init__(self, attrs=None, date_format=None, time_format=None, date_attrs=None, time_attrs=None): + template_name = "django/forms/widgets/splithiddendatetime.html" + + 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) for widget in self.widgets: - widget.input_type = 'hidden' + widget.input_type = "hidden" class SelectDateWidget(Widget): @@ -938,14 +1014,15 @@ class SelectDateWidget(Widget): This also serves as an example of a Widget that has more than one HTML element and hence implements value_from_datadict. """ - none_value = ('', '---') - month_field = '%s_month' - day_field = '%s_day' - year_field = '%s_year' - template_name = 'django/forms/widgets/select_date.html' - input_type = 'select' + + none_value = ("", "---") + month_field = "%s_month" + day_field = "%s_day" + year_field = "%s_year" + template_name = "django/forms/widgets/select_date.html" + input_type = "select" select_widget = Select - date_re = _lazy_re_compile(r'(\d{4}|0)-(\d\d?)-(\d\d?)$') + date_re = _lazy_re_compile(r"(\d{4}|0)-(\d\d?)-(\d\d?)$") def __init__(self, attrs=None, years=None, months=None, empty_label=None): self.attrs = attrs or {} @@ -966,14 +1043,14 @@ class SelectDateWidget(Widget): # 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.') + raise ValueError("empty_label list/tuple must have 3 elements.") - self.year_none_value = ('', empty_label[0]) - self.month_none_value = ('', empty_label[1]) - self.day_none_value = ('', empty_label[2]) + self.year_none_value = ("", empty_label[0]) + self.month_none_value = ("", empty_label[1]) + self.day_none_value = ("", empty_label[2]) else: if empty_label is not None: - self.none_value = ('', empty_label) + self.none_value = ("", empty_label) self.year_none_value = self.none_value self.month_none_value = self.none_value @@ -986,33 +1063,40 @@ class SelectDateWidget(Widget): if not self.is_required: 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( + date_context["year"] = self.select_widget( + attrs, choices=year_choices + ).get_context( name=year_name, - value=context['widget']['value']['year'], - attrs={**context['widget']['attrs'], 'id': 'id_%s' % year_name}, + value=context["widget"]["value"]["year"], + attrs={**context["widget"]["attrs"], "id": "id_%s" % year_name}, ) month_choices = list(self.months.items()) if not self.is_required: 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( + date_context["month"] = self.select_widget( + attrs, choices=month_choices + ).get_context( name=month_name, - value=context['widget']['value']['month'], - attrs={**context['widget']['attrs'], 'id': 'id_%s' % month_name}, + value=context["widget"]["value"]["month"], + attrs={**context["widget"]["attrs"], "id": "id_%s" % month_name}, ) day_choices = [(i, i) for i in range(1, 32)] if not self.is_required: 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( + date_context["day"] = self.select_widget( + attrs, + choices=day_choices, + ).get_context( name=day_name, - value=context['widget']['value']['day'], - attrs={**context['widget']['attrs'], 'id': 'id_%s' % day_name}, + value=context["widget"]["value"]["day"], + attrs={**context["widget"]["attrs"], "id": "id_%s" % day_name}, ) subwidgets = [] for field in self._parse_date_fmt(): - subwidgets.append(date_context[field]['widget']) - context['widget']['subwidgets'] = subwidgets + subwidgets.append(date_context[field]["widget"]) + context["widget"]["subwidgets"] = subwidgets return context def format_value(self, value): @@ -1029,58 +1113,58 @@ class SelectDateWidget(Widget): if match: # 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()] + year, month, day = [int(val) or "" for val in match.groups()] else: - input_format = get_format('DATE_INPUT_FORMATS')[0] + input_format = get_format("DATE_INPUT_FORMATS")[0] try: d = datetime.datetime.strptime(value, input_format) except ValueError: pass else: year, month, day = d.year, d.month, d.day - return {'year': year, 'month': month, 'day': day} + return {"year": year, "month": month, "day": day} @staticmethod def _parse_date_fmt(): - fmt = get_format('DATE_FORMAT') + fmt = get_format("DATE_FORMAT") escaped = False for char in fmt: if escaped: escaped = False - elif char == '\\': + elif char == "\\": escaped = True - elif char in 'Yy': - yield 'year' - elif char in 'bEFMmNn': - yield 'month' - elif char in 'dj': - yield 'day' + 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) - return '%s_month' % id_ + return "%s_%s" % (id_, first_select) + return "%s_month" % id_ 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) - if y == m == d == '': + if y == m == d == "": return None if y is not None and m is not None and d is not None: - input_format = get_format('DATE_INPUT_FORMATS')[0] + input_format = get_format("DATE_INPUT_FORMATS")[0] input_format = formats.sanitize_strftime_format(input_format) 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 "%s-%s-%s" % (y or 0, m or 0, d or 0) return date_value.strftime(input_format) return data.get(name) def value_omitted_from_data(self, data, files, name): return not any( - ('{}_{}'.format(name, interval) in data) - for interval in ('year', 'month', 'day') + ("{}_{}".format(name, interval) in data) + for interval in ("year", "month", "day") ) |
