Buckets:
| """ | |
| HTML Widget classes | |
| """ | |
| import copy | |
| import datetime | |
| import warnings | |
| from collections import defaultdict | |
| from graphlib import CycleError, TopologicalSorter | |
| from itertools import chain | |
| from django.forms.utils import flatatt, to_current_timezone | |
| from django.templatetags.static import static | |
| from django.utils import formats | |
| from django.utils.choices import normalize_choices | |
| from django.utils.dates import MONTHS | |
| 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.translation import gettext_lazy as _ | |
| from .renderers import get_default_renderer | |
| __all__ = ( | |
| "Script", | |
| "Media", | |
| "MediaDefiningClass", | |
| "Widget", | |
| "TextInput", | |
| "NumberInput", | |
| "EmailInput", | |
| "URLInput", | |
| "ColorInput", | |
| "SearchInput", | |
| "TelInput", | |
| "PasswordInput", | |
| "HiddenInput", | |
| "MultipleHiddenInput", | |
| "FileInput", | |
| "ClearableFileInput", | |
| "Textarea", | |
| "DateInput", | |
| "DateTimeInput", | |
| "TimeInput", | |
| "CheckboxInput", | |
| "Select", | |
| "NullBooleanSelect", | |
| "SelectMultiple", | |
| "RadioSelect", | |
| "CheckboxSelectMultiple", | |
| "MultiWidget", | |
| "SplitDateTimeWidget", | |
| "SplitHiddenDateTimeWidget", | |
| "SelectDateWidget", | |
| ) | |
| MEDIA_TYPES = ("css", "js") | |
| class MediaOrderConflictWarning(RuntimeWarning): | |
| pass | |
| class MediaAsset: | |
| element_template = "{path}" | |
| def __init__(self, path, **attributes): | |
| self._path = path | |
| self.attributes = attributes | |
| def __eq__(self, other): | |
| # Compare the path only, to ensure performant comparison in | |
| # Media.merge. | |
| return (self.__class__ is other.__class__ and self.path == other.path) or ( | |
| isinstance(other, str) and self._path == other | |
| ) | |
| def __hash__(self): | |
| # Hash the path only, to ensure performant comparison in Media.merge. | |
| return hash(self._path) | |
| def __str__(self): | |
| return format_html( | |
| self.element_template, | |
| path=self.path, | |
| attributes=flatatt(self.attributes), | |
| ) | |
| def __repr__(self): | |
| return f"{type(self).__qualname__}({self._path!r})" | |
| def path(self): | |
| """ | |
| Ensure an absolute path. | |
| Relative paths are resolved via the {% static %} template tag. | |
| """ | |
| if self._path.startswith(("http://", "https://", "/")): | |
| return self._path | |
| return static(self._path) | |
| class Script(MediaAsset): | |
| element_template = '<script src="{path}"{attributes}></script>' | |
| def __init__(self, src, **attributes): | |
| # Alter the signature to allow src to be passed as a keyword argument. | |
| super().__init__(src, **attributes) | |
| class Media: | |
| def __init__(self, media=None, css=None, js=None): | |
| if media is not None: | |
| css = getattr(media, "css", {}) | |
| js = getattr(media, "js", []) | |
| else: | |
| if css is None: | |
| css = {} | |
| if js is None: | |
| js = [] | |
| self._css_lists = [css] | |
| self._js_lists = [js] | |
| def __repr__(self): | |
| return "Media(css=%r, js=%r)" % (self._css, self._js) | |
| def __str__(self): | |
| return self.render() | |
| def _css(self): | |
| 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()} | |
| def _js(self): | |
| 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 | |
| ) | |
| ) | |
| ) | |
| def render_js(self): | |
| return [ | |
| ( | |
| path.__html__() | |
| if hasattr(path, "__html__") | |
| else 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( | |
| [ | |
| ( | |
| path.__html__() | |
| if hasattr(path, "__html__") | |
| else 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): | |
| """ | |
| 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(). | |
| """ | |
| 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)}) | |
| raise KeyError('Unknown media type "%s"' % name) | |
| def merge(*lists): | |
| """ | |
| 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. | |
| 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. | |
| """ | |
| ts = TopologicalSorter() | |
| for head, *tail in filter(None, lists): | |
| ts.add(head) # Ensure that the first items are included. | |
| for item in tail: | |
| if head != item: # Avoid circular dependency to self. | |
| ts.add(item, head) | |
| head = item | |
| try: | |
| return list(ts.static_order()) | |
| except CycleError: | |
| warnings.warn( | |
| "Detected duplicate Media files in an opposite order: {}".format( | |
| ", ".join(repr(list_) for list_ in lists) | |
| ), | |
| MediaOrderConflictWarning, | |
| ) | |
| return list(dict.fromkeys(chain.from_iterable(filter(None, lists)))) | |
| def __add__(self, other): | |
| combined = Media() | |
| 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) | |
| return combined | |
| def media_property(cls): | |
| def _media(self): | |
| # Get the media property of the superclass, if it exists | |
| sup_cls = super(cls, self) | |
| try: | |
| base = sup_cls.media | |
| except AttributeError: | |
| base = Media() | |
| # Get the media definition for this class | |
| definition = getattr(cls, "Media", None) | |
| if definition: | |
| extend = getattr(definition, "extend", True) | |
| if extend: | |
| if extend is True: | |
| m = base | |
| else: | |
| m = Media() | |
| for medium in extend: | |
| m += base[medium] | |
| return m + Media(definition) | |
| return Media(definition) | |
| return base | |
| return property(_media) | |
| 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: | |
| new_class.media = media_property(new_class) | |
| return new_class | |
| class Widget(metaclass=MediaDefiningClass): | |
| needs_multipart_form = False # Determines does this widget need multipart form | |
| is_localized = False | |
| is_required = False | |
| supports_microseconds = True | |
| use_fieldset = False | |
| def __init__(self, attrs=None): | |
| self.attrs = {} if attrs is None else attrs.copy() | |
| def __deepcopy__(self, memo): | |
| obj = copy.copy(self) | |
| obj.attrs = self.attrs.copy() | |
| memo[id(self)] = obj | |
| return obj | |
| def is_hidden(self): | |
| 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"] | |
| def format_value(self, value): | |
| """ | |
| Return a value as it should appear when rendered in a template. | |
| """ | |
| if value == "" or value is None: | |
| return None | |
| if self.is_localized: | |
| return formats.localize_input(value) | |
| return str(value) | |
| 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, | |
| }, | |
| } | |
| def render(self, name, value, attrs=None, renderer=None): | |
| """Render the widget as an HTML string.""" | |
| 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)) | |
| def build_attrs(self, base_attrs, extra_attrs=None): | |
| """Build an attribute dictionary.""" | |
| return {**base_attrs, **(extra_attrs or {})} | |
| def value_from_datadict(self, data, files, name): | |
| """ | |
| Given a dictionary of data and this widget's name, return the value | |
| of this widget or None if it's not provided. | |
| """ | |
| return data.get(name) | |
| def value_omitted_from_data(self, data, files, name): | |
| return name not in data | |
| def id_for_label(self, id_): | |
| """ | |
| 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. | |
| 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_ | |
| def use_required_attribute(self, initial): | |
| return not self.is_hidden | |
| class Input(Widget): | |
| """ | |
| Base class for all <input> widgets. | |
| """ | |
| input_type = None # Subclasses must define this. | |
| 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) | |
| super().__init__(attrs) | |
| def get_context(self, name, value, attrs): | |
| context = super().get_context(name, value, attrs) | |
| context["widget"]["type"] = self.input_type | |
| return context | |
| class TextInput(Input): | |
| input_type = "text" | |
| template_name = "django/forms/widgets/text.html" | |
| class NumberInput(Input): | |
| input_type = "number" | |
| template_name = "django/forms/widgets/number.html" | |
| class EmailInput(Input): | |
| input_type = "email" | |
| template_name = "django/forms/widgets/email.html" | |
| class URLInput(Input): | |
| input_type = "url" | |
| template_name = "django/forms/widgets/url.html" | |
| class ColorInput(Input): | |
| input_type = "color" | |
| template_name = "django/forms/widgets/color.html" | |
| class SearchInput(Input): | |
| input_type = "search" | |
| template_name = "django/forms/widgets/search.html" | |
| class TelInput(Input): | |
| input_type = "tel" | |
| template_name = "django/forms/widgets/tel.html" | |
| class PasswordInput(Input): | |
| input_type = "password" | |
| template_name = "django/forms/widgets/password.html" | |
| def __init__(self, attrs=None, render_value=False): | |
| super().__init__(attrs) | |
| self.render_value = render_value | |
| def get_context(self, name, value, attrs): | |
| if not self.render_value: | |
| value = None | |
| return super().get_context(name, value, attrs) | |
| class HiddenInput(Input): | |
| input_type = "hidden" | |
| template_name = "django/forms/widgets/hidden.html" | |
| class MultipleHiddenInput(HiddenInput): | |
| """ | |
| Handle <input type="hidden"> for fields that have a list | |
| of values. | |
| """ | |
| 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") | |
| subwidgets = [] | |
| 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 = HiddenInput() | |
| widget.is_required = self.is_required | |
| subwidgets.append(widget.get_context(name, value_, widget_attrs)["widget"]) | |
| context["widget"]["subwidgets"] = subwidgets | |
| return context | |
| def value_from_datadict(self, data, files, name): | |
| try: | |
| getter = data.getlist | |
| except AttributeError: | |
| getter = data.get | |
| return getter(name) | |
| def format_value(self, value): | |
| return [] if value is None else value | |
| class FileInput(Input): | |
| allow_multiple_selected = False | |
| input_type = "file" | |
| needs_multipart_form = True | |
| template_name = "django/forms/widgets/file.html" | |
| def __init__(self, attrs=None): | |
| if ( | |
| attrs is not None | |
| and not self.allow_multiple_selected | |
| and attrs.get("multiple", False) | |
| ): | |
| raise ValueError( | |
| "%s doesn't support uploading multiple files." | |
| % self.__class__.__qualname__ | |
| ) | |
| if self.allow_multiple_selected: | |
| if attrs is None: | |
| attrs = {"multiple": True} | |
| else: | |
| attrs.setdefault("multiple", True) | |
| super().__init__(attrs) | |
| def format_value(self, value): | |
| """File input never renders a value.""" | |
| return | |
| def value_from_datadict(self, data, files, name): | |
| "File widgets take data from FILES, not POST" | |
| getter = files.get | |
| if self.allow_multiple_selected: | |
| try: | |
| getter = files.getlist | |
| except AttributeError: | |
| pass | |
| return getter(name) | |
| def value_omitted_from_data(self, data, files, name): | |
| return name not in files | |
| def use_required_attribute(self, initial): | |
| return super().use_required_attribute(initial) and not initial | |
| 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" | |
| checked = False | |
| use_fieldset = False | |
| 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" | |
| def is_initial(self, value): | |
| """ | |
| Return whether value is considered to be initial value. | |
| """ | |
| return bool(value and getattr(value, "url", False)) | |
| def format_value(self, value): | |
| """ | |
| Return the file object if it has a defined url attribute. | |
| """ | |
| if self.is_initial(value): | |
| return value | |
| def get_context(self, name, value, attrs): | |
| 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"]["attrs"].setdefault("disabled", False) | |
| context["widget"]["attrs"]["checked"] = self.checked | |
| return context | |
| def value_from_datadict(self, data, files, name): | |
| upload = super().value_from_datadict(data, files, name) | |
| self.checked = self.clear_checkbox_name(name) in data | |
| if not self.is_required and CheckboxInput().value_from_datadict( | |
| data, files, self.clear_checkbox_name(name) | |
| ): | |
| 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 | |
| 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 | |
| ) | |
| class Textarea(Widget): | |
| 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"} | |
| if attrs: | |
| default_attrs.update(attrs) | |
| super().__init__(default_attrs) | |
| class DateTimeBaseInput(TextInput): | |
| format_key = "" | |
| supports_microseconds = False | |
| def __init__(self, attrs=None, format=None): | |
| super().__init__(attrs) | |
| 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] | |
| ) | |
| class DateInput(DateTimeBaseInput): | |
| 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" | |
| class TimeInput(DateTimeBaseInput): | |
| 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 == "") | |
| class CheckboxInput(Input): | |
| input_type = "checkbox" | |
| template_name = "django/forms/widgets/checkbox.html" | |
| def __init__(self, attrs=None, check_test=None): | |
| super().__init__(attrs) | |
| # check_test is a callable that takes a value and returns True | |
| # if the checkbox should be checked for that value. | |
| self.check_test = boolean_check if check_test is None else check_test | |
| 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 | |
| return str(value) | |
| def get_context(self, name, value, attrs): | |
| if self.check_test(value): | |
| attrs = {**(attrs or {}), "checked": True} | |
| return super().get_context(name, value, attrs) | |
| 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 | |
| value = data.get(name) | |
| # Translate true and false strings to boolean values. | |
| values = {"true": True, "false": False} | |
| if isinstance(value, str): | |
| value = values.get(value.lower(), value) | |
| return bool(value) | |
| 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 | |
| class ChoiceWidget(Widget): | |
| allow_multiple_selected = False | |
| input_type = None | |
| template_name = None | |
| option_template_name = None | |
| add_id_index = True | |
| checked_attribute = {"checked": True} | |
| option_inherits_attrs = True | |
| def __init__(self, attrs=None, choices=()): | |
| super().__init__(attrs) | |
| self.choices = choices | |
| 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 | |
| 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) | |
| yield from self.options(name, value, attrs) | |
| def options(self, name, value, attrs=None): | |
| """Yield a flat list of options for this widget.""" | |
| for group in self.optgroups(name, value, attrs): | |
| yield from group[1] | |
| def optgroups(self, name, value, attrs=None): | |
| """Return a list of optgroups for this widget.""" | |
| groups = [] | |
| has_selected = False | |
| for index, (option_value, option_label) in enumerate(self.choices): | |
| if option_value is None: | |
| option_value = "" | |
| subgroup = [] | |
| if isinstance(option_label, (list, tuple)): | |
| group_name = option_value | |
| subindex = 0 | |
| choices = option_label | |
| else: | |
| group_name = None | |
| subindex = None | |
| choices = [(option_value, option_label)] | |
| 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 | |
| has_selected |= selected | |
| 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 | |
| ): | |
| 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 {} | |
| ) | |
| if selected: | |
| option_attrs.update(self.checked_attribute) | |
| 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, | |
| } | |
| def get_context(self, name, value, attrs): | |
| context = super().get_context(name, value, attrs) | |
| 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: | |
| try: | |
| getter = data.getlist | |
| except AttributeError: | |
| pass | |
| return getter(name) | |
| def format_value(self, value): | |
| """Return selected values as a list.""" | |
| if value is None and self.allow_multiple_selected: | |
| return [] | |
| if not isinstance(value, (tuple, list)): | |
| value = [value] | |
| return [str(v) if v is not None else "" for v in value] | |
| def choices(self): | |
| return self._choices | |
| def choices(self, value): | |
| self._choices = normalize_choices(value) | |
| 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 | |
| def get_context(self, name, value, attrs): | |
| context = super().get_context(name, value, attrs) | |
| if self.allow_multiple_selected: | |
| context["widget"]["attrs"]["multiple"] = True | |
| return context | |
| 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 == "" | |
| def use_required_attribute(self, initial): | |
| """ | |
| Don't render 'required' if the first <option> has a value, as that's | |
| invalid HTML. | |
| """ | |
| use_required_attribute = super().use_required_attribute(initial) | |
| # '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) | |
| ) | |
| class NullBooleanSelect(Select): | |
| """ | |
| A Select Widget intended to be used with NullBooleanField. | |
| """ | |
| def __init__(self, attrs=None): | |
| choices = ( | |
| ("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", | |
| # For backwards compatibility with Django < 2.2. | |
| "2": "true", | |
| "3": "false", | |
| }[value] | |
| except KeyError: | |
| return "unknown" | |
| def value_from_datadict(self, data, files, name): | |
| value = data.get(name) | |
| return { | |
| True: True, | |
| "True": True, | |
| "False": False, | |
| False: False, | |
| "true": True, | |
| "false": False, | |
| # For backwards compatibility with Django < 2.2. | |
| "2": True, | |
| "3": False, | |
| }.get(value) | |
| class SelectMultiple(Select): | |
| allow_multiple_selected = True | |
| def value_from_datadict(self, data, files, name): | |
| try: | |
| getter = data.getlist | |
| except AttributeError: | |
| getter = data.get | |
| return getter(name) | |
| 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 | |
| class RadioSelect(ChoiceWidget): | |
| input_type = "radio" | |
| template_name = "django/forms/widgets/radio.html" | |
| option_template_name = "django/forms/widgets/radio_option.html" | |
| use_fieldset = True | |
| 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) | |
| 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" | |
| def use_required_attribute(self, initial): | |
| # Don't use the 'required' attribute because browser validation would | |
| # require all checkboxes to be checked instead of at least one. | |
| return False | |
| 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 | |
| class MultiWidget(Widget): | |
| """ | |
| A widget that is composed of multiple widgets. | |
| 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. | |
| You'll probably want to use this class with MultiValueField. | |
| """ | |
| template_name = "django/forms/widgets/multiwidget.html" | |
| use_fieldset = True | |
| def __init__(self, widgets, attrs=None): | |
| 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))] | |
| self.widgets = [w() if isinstance(w, type) else w for w in widgets] | |
| super().__init__(attrs) | |
| def is_hidden(self): | |
| return all(w.is_hidden for w in self.widgets) | |
| def get_context(self, name, value, attrs): | |
| context = super().get_context(name, value, attrs) | |
| if self.is_localized: | |
| for widget in self.widgets: | |
| widget.is_localized = self.is_localized | |
| # value is a list/tuple of values, each corresponding to a widget | |
| # in self.widgets. | |
| if not isinstance(value, (list, tuple)): | |
| value = self.decompress(value) | |
| 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) | |
| ): | |
| if input_type is not None: | |
| widget.input_type = input_type | |
| widget_name = name + widget_name | |
| try: | |
| widget_value = value[i] | |
| except IndexError: | |
| widget_value = None | |
| if id_: | |
| 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 | |
| def id_for_label(self, id_): | |
| return "" | |
| def value_from_datadict(self, data, files, name): | |
| return [ | |
| widget.value_from_datadict(data, files, name + widget_name) | |
| for widget_name, widget in zip(self.widgets_names, self.widgets) | |
| ] | |
| def value_omitted_from_data(self, data, files, name): | |
| return all( | |
| widget.value_omitted_from_data(data, files, name + widget_name) | |
| for widget_name, widget in zip(self.widgets_names, self.widgets) | |
| ) | |
| def decompress(self, value): | |
| """ | |
| Return a list of decompressed values for the given compressed value. | |
| The given value can be assumed to be valid, but not necessarily | |
| non-empty. | |
| """ | |
| raise NotImplementedError("Subclasses must implement this method.") | |
| def _get_media(self): | |
| """ | |
| Media for a multiwidget is the combination of all media of the | |
| subwidgets. | |
| """ | |
| media = Media() | |
| for w in self.widgets: | |
| media += w.media | |
| return media | |
| media = property(_get_media) | |
| def __deepcopy__(self, memo): | |
| obj = super().__deepcopy__(memo) | |
| obj.widgets = copy.deepcopy(self.widgets) | |
| return obj | |
| def needs_multipart_form(self): | |
| return any(w.needs_multipart_form for w in self.widgets) | |
| 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" | |
| 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, | |
| format=date_format, | |
| ), | |
| TimeInput( | |
| attrs=attrs if time_attrs is None else time_attrs, | |
| format=time_format, | |
| ), | |
| ) | |
| super().__init__(widgets) | |
| def decompress(self, value): | |
| if value: | |
| value = to_current_timezone(value) | |
| return [value.date(), value.time()] | |
| return [None, None] | |
| 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, | |
| ): | |
| super().__init__(attrs, date_format, time_format, date_attrs, time_attrs) | |
| for widget in self.widgets: | |
| widget.input_type = "hidden" | |
| class SelectDateWidget(Widget): | |
| """ | |
| A widget that splits date input into three <select> boxes. | |
| 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" | |
| select_widget = Select | |
| date_re = _lazy_re_compile(r"(\d{4}|0)-(\d\d?)-(\d\d?)$") | |
| use_fieldset = True | |
| 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.") | |
| 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.year_none_value = self.none_value | |
| self.month_none_value = self.none_value | |
| self.day_none_value = self.none_value | |
| def get_context(self, name, value, attrs): | |
| context = super().get_context(name, value, attrs) | |
| date_context = {} | |
| year_choices = [(i, str(i)) for i in self.years] | |
| 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( | |
| name=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( | |
| name=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( | |
| name=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 | |
| 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 | |
| elif isinstance(value, str): | |
| match = self.date_re.match(value) | |
| 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()] | |
| else: | |
| 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} | |
| 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) | |
| 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 == "": | |
| 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 = 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) | |
| except OverflowError: | |
| return "0-0-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") | |
| ) | |
Xet Storage Details
- Size:
- 41.3 kB
- Xet hash:
- 8981e7578b90966bc99062f5b9f88899b2d6282870c58bfbd1fa21324aacd4aa
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.