Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.10/site-packages/django/forms/widgets.py: 38%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

687 statements  

1""" 

2HTML Widget classes 

3""" 

4 

5import copy 

6import datetime 

7import warnings 

8from collections import defaultdict 

9from graphlib import CycleError, TopologicalSorter 

10from itertools import chain 

11 

12from django.forms.utils import flatatt, to_current_timezone 

13from django.templatetags.static import static 

14from django.utils import formats 

15from django.utils.choices import normalize_choices 

16from django.utils.dates import MONTHS 

17from django.utils.formats import get_format 

18from django.utils.html import format_html, html_safe 

19from django.utils.regex_helper import _lazy_re_compile 

20from django.utils.safestring import mark_safe 

21from django.utils.translation import gettext_lazy as _ 

22 

23from .renderers import get_default_renderer 

24 

25__all__ = ( 

26 "Script", 

27 "Media", 

28 "MediaDefiningClass", 

29 "Widget", 

30 "TextInput", 

31 "NumberInput", 

32 "EmailInput", 

33 "URLInput", 

34 "ColorInput", 

35 "SearchInput", 

36 "TelInput", 

37 "PasswordInput", 

38 "HiddenInput", 

39 "MultipleHiddenInput", 

40 "FileInput", 

41 "ClearableFileInput", 

42 "Textarea", 

43 "DateInput", 

44 "DateTimeInput", 

45 "TimeInput", 

46 "CheckboxInput", 

47 "Select", 

48 "NullBooleanSelect", 

49 "SelectMultiple", 

50 "RadioSelect", 

51 "CheckboxSelectMultiple", 

52 "MultiWidget", 

53 "SplitDateTimeWidget", 

54 "SplitHiddenDateTimeWidget", 

55 "SelectDateWidget", 

56) 

57 

58MEDIA_TYPES = ("css", "js") 

59 

60 

61class MediaOrderConflictWarning(RuntimeWarning): 

62 pass 

63 

64 

65@html_safe 

66class MediaAsset: 

67 element_template = "{path}" 

68 

69 def __init__(self, path, **attributes): 

70 self._path = path 

71 self.attributes = attributes 

72 

73 def __eq__(self, other): 

74 # Compare the path only, to ensure performant comparison in Media.merge. 

75 return (self.__class__ is other.__class__ and self.path == other.path) or ( 

76 isinstance(other, str) and self._path == other 

77 ) 

78 

79 def __hash__(self): 

80 # Hash the path only, to ensure performant comparison in Media.merge. 

81 return hash(self._path) 

82 

83 def __str__(self): 

84 return format_html( 

85 self.element_template, 

86 path=self.path, 

87 attributes=flatatt(self.attributes), 

88 ) 

89 

90 def __repr__(self): 

91 return f"{type(self).__qualname__}({self._path!r})" 

92 

93 @property 

94 def path(self): 

95 """ 

96 Ensure an absolute path. 

97 Relative paths are resolved via the {% static %} template tag. 

98 """ 

99 if self._path.startswith(("http://", "https://", "/")): 

100 return self._path 

101 return static(self._path) 

102 

103 

104class Script(MediaAsset): 

105 element_template = '<script src="{path}"{attributes}></script>' 

106 

107 def __init__(self, src, **attributes): 

108 # Alter the signature to allow src to be passed as a keyword argument. 

109 super().__init__(src, **attributes) 

110 

111 

112@html_safe 

113class Media: 

114 def __init__(self, media=None, css=None, js=None): 

115 if media is not None: 

116 css = getattr(media, "css", {}) 

117 js = getattr(media, "js", []) 

118 else: 

119 if css is None: 

120 css = {} 

121 if js is None: 

122 js = [] 

123 self._css_lists = [css] 

124 self._js_lists = [js] 

125 

126 def __repr__(self): 

127 return "Media(css=%r, js=%r)" % (self._css, self._js) 

128 

129 def __str__(self): 

130 return self.render() 

131 

132 @property 

133 def _css(self): 

134 css = defaultdict(list) 

135 for css_list in self._css_lists: 

136 for medium, sublist in css_list.items(): 

137 css[medium].append(sublist) 

138 return {medium: self.merge(*lists) for medium, lists in css.items()} 

139 

140 @property 

141 def _js(self): 

142 return self.merge(*self._js_lists) 

143 

144 def render(self): 

145 return mark_safe( 

146 "\n".join( 

147 chain.from_iterable( 

148 getattr(self, "render_" + name)() for name in MEDIA_TYPES 

149 ) 

150 ) 

151 ) 

152 

153 def render_js(self): 

154 return [ 

155 ( 

156 path.__html__() 

157 if hasattr(path, "__html__") 

158 else format_html('<script src="{}"></script>', self.absolute_path(path)) 

159 ) 

160 for path in self._js 

161 ] 

162 

163 def render_css(self): 

164 # To keep rendering order consistent, we can't just iterate over items(). 

165 # We need to sort the keys, and iterate over the sorted list. 

166 media = sorted(self._css) 

167 return chain.from_iterable( 

168 [ 

169 ( 

170 path.__html__() 

171 if hasattr(path, "__html__") 

172 else format_html( 

173 '<link href="{}" media="{}" rel="stylesheet">', 

174 self.absolute_path(path), 

175 medium, 

176 ) 

177 ) 

178 for path in self._css[medium] 

179 ] 

180 for medium in media 

181 ) 

182 

183 def absolute_path(self, path): 

184 """ 

185 Given a relative or absolute path to a static asset, return an absolute 

186 path. An absolute path will be returned unchanged while a relative path 

187 will be passed to django.templatetags.static.static(). 

188 """ 

189 if path.startswith(("http://", "https://", "/")): 

190 return path 

191 return static(path) 

192 

193 def __getitem__(self, name): 

194 """Return a Media object that only contains media of the given type.""" 

195 if name in MEDIA_TYPES: 

196 return Media(**{str(name): getattr(self, "_" + name)}) 

197 raise KeyError('Unknown media type "%s"' % name) 

198 

199 @staticmethod 

200 def merge(*lists): 

201 """ 

202 Merge lists while trying to keep the relative order of the elements. 

203 Warn if the lists have the same elements in a different relative order. 

204 

205 For static assets it can be important to have them included in the DOM 

206 in a certain order. In JavaScript you may not be able to reference a 

207 global or in CSS you might want to override a style. 

208 """ 

209 ts = TopologicalSorter() 

210 for head, *tail in filter(None, lists): 

211 ts.add(head) # Ensure that the first items are included. 

212 for item in tail: 

213 if head != item: # Avoid circular dependency to self. 

214 ts.add(item, head) 

215 head = item 

216 try: 

217 return list(ts.static_order()) 

218 except CycleError: 

219 warnings.warn( 

220 "Detected duplicate Media files in an opposite order: {}".format( 

221 ", ".join(repr(list_) for list_ in lists) 

222 ), 

223 MediaOrderConflictWarning, 

224 ) 

225 return list(dict.fromkeys(chain.from_iterable(filter(None, lists)))) 

226 

227 def __add__(self, other): 

228 combined = Media() 

229 combined._css_lists = self._css_lists[:] 

230 combined._js_lists = self._js_lists[:] 

231 for item in other._css_lists: 

232 if item and item not in self._css_lists: 

233 combined._css_lists.append(item) 

234 for item in other._js_lists: 

235 if item and item not in self._js_lists: 

236 combined._js_lists.append(item) 

237 return combined 

238 

239 

240def media_property(cls): 

241 def _media(self): 

242 # Get the media property of the superclass, if it exists 

243 sup_cls = super(cls, self) 

244 try: 

245 base = sup_cls.media 

246 except AttributeError: 

247 base = Media() 

248 

249 # Get the media definition for this class 

250 definition = getattr(cls, "Media", None) 

251 if definition: 

252 extend = getattr(definition, "extend", True) 

253 if extend: 

254 if extend is True: 

255 m = base 

256 else: 

257 m = Media() 

258 for medium in extend: 

259 m += base[medium] 

260 return m + Media(definition) 

261 return Media(definition) 

262 return base 

263 

264 return property(_media) 

265 

266 

267class MediaDefiningClass(type): 

268 """ 

269 Metaclass for classes that can have media definitions. 

270 """ 

271 

272 def __new__(mcs, name, bases, attrs): 

273 new_class = super().__new__(mcs, name, bases, attrs) 

274 

275 if "media" not in attrs: 

276 new_class.media = media_property(new_class) 

277 

278 return new_class 

279 

280 

281class Widget(metaclass=MediaDefiningClass): 

282 needs_multipart_form = False # Determines does this widget need multipart form 

283 is_localized = False 

284 is_required = False 

285 supports_microseconds = True 

286 use_fieldset = False 

287 

288 def __init__(self, attrs=None): 

289 self.attrs = {} if attrs is None else attrs.copy() 

290 

291 def __deepcopy__(self, memo): 

292 obj = copy.copy(self) 

293 obj.attrs = self.attrs.copy() 

294 memo[id(self)] = obj 

295 return obj 

296 

297 @property 

298 def is_hidden(self): 

299 return self.input_type == "hidden" if hasattr(self, "input_type") else False 

300 

301 def subwidgets(self, name, value, attrs=None): 

302 context = self.get_context(name, value, attrs) 

303 yield context["widget"] 

304 

305 def format_value(self, value): 

306 """ 

307 Return a value as it should appear when rendered in a template. 

308 """ 

309 if value == "" or value is None: 

310 return None 

311 if self.is_localized: 

312 return formats.localize_input(value) 

313 return str(value) 

314 

315 def get_context(self, name, value, attrs): 

316 return { 

317 "widget": { 

318 "name": name, 

319 "is_hidden": self.is_hidden, 

320 "required": self.is_required, 

321 "value": self.format_value(value), 

322 "attrs": self.build_attrs(self.attrs, attrs), 

323 "template_name": self.template_name, 

324 }, 

325 } 

326 

327 def render(self, name, value, attrs=None, renderer=None): 

328 """Render the widget as an HTML string.""" 

329 context = self.get_context(name, value, attrs) 

330 return self._render(self.template_name, context, renderer) 

331 

332 def _render(self, template_name, context, renderer=None): 

333 if renderer is None: 

334 renderer = get_default_renderer() 

335 return mark_safe(renderer.render(template_name, context)) 

336 

337 def build_attrs(self, base_attrs, extra_attrs=None): 

338 """Build an attribute dictionary.""" 

339 return {**base_attrs, **(extra_attrs or {})} 

340 

341 def value_from_datadict(self, data, files, name): 

342 """ 

343 Given a dictionary of data and this widget's name, return the value 

344 of this widget or None if it's not provided. 

345 """ 

346 return data.get(name) 

347 

348 def value_omitted_from_data(self, data, files, name): 

349 return name not in data 

350 

351 def id_for_label(self, id_): 

352 """ 

353 Return the HTML ID attribute of this Widget for use by a <label>, given 

354 the ID of the field. Return an empty string if no ID is available. 

355 

356 This hook is necessary because some widgets have multiple HTML 

357 elements and, thus, multiple IDs. In that case, this method should 

358 return an ID value that corresponds to the first ID in the widget's 

359 tags. 

360 """ 

361 return id_ 

362 

363 def use_required_attribute(self, initial): 

364 return not self.is_hidden 

365 

366 

367class Input(Widget): 

368 """ 

369 Base class for all <input> widgets. 

370 """ 

371 

372 input_type = None # Subclasses must define this. 

373 template_name = "django/forms/widgets/input.html" 

374 

375 def __init__(self, attrs=None): 

376 if attrs is not None: 

377 attrs = attrs.copy() 

378 self.input_type = attrs.pop("type", self.input_type) 

379 super().__init__(attrs) 

380 

381 def get_context(self, name, value, attrs): 

382 context = super().get_context(name, value, attrs) 

383 context["widget"]["type"] = self.input_type 

384 return context 

385 

386 

387class TextInput(Input): 

388 input_type = "text" 

389 template_name = "django/forms/widgets/text.html" 

390 

391 

392class NumberInput(Input): 

393 input_type = "number" 

394 template_name = "django/forms/widgets/number.html" 

395 

396 

397class EmailInput(Input): 

398 input_type = "email" 

399 template_name = "django/forms/widgets/email.html" 

400 

401 

402class URLInput(Input): 

403 input_type = "url" 

404 template_name = "django/forms/widgets/url.html" 

405 

406 

407class ColorInput(Input): 

408 input_type = "color" 

409 template_name = "django/forms/widgets/color.html" 

410 

411 

412class SearchInput(Input): 

413 input_type = "search" 

414 template_name = "django/forms/widgets/search.html" 

415 

416 

417class TelInput(Input): 

418 input_type = "tel" 

419 template_name = "django/forms/widgets/tel.html" 

420 

421 

422class PasswordInput(Input): 

423 input_type = "password" 

424 template_name = "django/forms/widgets/password.html" 

425 

426 def __init__(self, attrs=None, render_value=False): 

427 super().__init__(attrs) 

428 self.render_value = render_value 

429 

430 def get_context(self, name, value, attrs): 

431 if not self.render_value: 

432 value = None 

433 return super().get_context(name, value, attrs) 

434 

435 

436class HiddenInput(Input): 

437 input_type = "hidden" 

438 template_name = "django/forms/widgets/hidden.html" 

439 

440 

441class MultipleHiddenInput(HiddenInput): 

442 """ 

443 Handle <input type="hidden"> for fields that have a list 

444 of values. 

445 """ 

446 

447 template_name = "django/forms/widgets/multiple_hidden.html" 

448 

449 def get_context(self, name, value, attrs): 

450 context = super().get_context(name, value, attrs) 

451 final_attrs = context["widget"]["attrs"] 

452 id_ = context["widget"]["attrs"].get("id") 

453 

454 subwidgets = [] 

455 for index, value_ in enumerate(context["widget"]["value"]): 

456 widget_attrs = final_attrs.copy() 

457 if id_: 

458 # An ID attribute was given. Add a numeric index as a suffix 

459 # so that the inputs don't all have the same ID attribute. 

460 widget_attrs["id"] = "%s_%s" % (id_, index) 

461 widget = HiddenInput() 

462 widget.is_required = self.is_required 

463 subwidgets.append(widget.get_context(name, value_, widget_attrs)["widget"]) 

464 

465 context["widget"]["subwidgets"] = subwidgets 

466 return context 

467 

468 def value_from_datadict(self, data, files, name): 

469 try: 

470 getter = data.getlist 

471 except AttributeError: 

472 getter = data.get 

473 return getter(name) 

474 

475 def format_value(self, value): 

476 return [] if value is None else value 

477 

478 

479class FileInput(Input): 

480 allow_multiple_selected = False 

481 input_type = "file" 

482 needs_multipart_form = True 

483 template_name = "django/forms/widgets/file.html" 

484 

485 def __init__(self, attrs=None): 

486 if ( 

487 attrs is not None 

488 and not self.allow_multiple_selected 

489 and attrs.get("multiple", False) 

490 ): 

491 raise ValueError( 

492 "%s doesn't support uploading multiple files." 

493 % self.__class__.__qualname__ 

494 ) 

495 if self.allow_multiple_selected: 

496 if attrs is None: 

497 attrs = {"multiple": True} 

498 else: 

499 attrs.setdefault("multiple", True) 

500 super().__init__(attrs) 

501 

502 def format_value(self, value): 

503 """File input never renders a value.""" 

504 return 

505 

506 def value_from_datadict(self, data, files, name): 

507 "File widgets take data from FILES, not POST" 

508 getter = files.get 

509 if self.allow_multiple_selected: 

510 try: 

511 getter = files.getlist 

512 except AttributeError: 

513 pass 

514 return getter(name) 

515 

516 def value_omitted_from_data(self, data, files, name): 

517 return name not in files 

518 

519 def use_required_attribute(self, initial): 

520 return super().use_required_attribute(initial) and not initial 

521 

522 

523FILE_INPUT_CONTRADICTION = object() 

524 

525 

526class ClearableFileInput(FileInput): 

527 clear_checkbox_label = _("Clear") 

528 initial_text = _("Currently") 

529 input_text = _("Change") 

530 template_name = "django/forms/widgets/clearable_file_input.html" 

531 checked = False 

532 

533 def clear_checkbox_name(self, name): 

534 """ 

535 Given the name of the file input, return the name of the clear checkbox 

536 input. 

537 """ 

538 return name + "-clear" 

539 

540 def clear_checkbox_id(self, name): 

541 """ 

542 Given the name of the clear checkbox input, return the HTML id for it. 

543 """ 

544 return name + "_id" 

545 

546 def is_initial(self, value): 

547 """ 

548 Return whether value is considered to be initial value. 

549 """ 

550 return bool(value and getattr(value, "url", False)) 

551 

552 def format_value(self, value): 

553 """ 

554 Return the file object if it has a defined url attribute. 

555 """ 

556 if self.is_initial(value): 

557 return value 

558 

559 def get_context(self, name, value, attrs): 

560 context = super().get_context(name, value, attrs) 

561 checkbox_name = self.clear_checkbox_name(name) 

562 checkbox_id = self.clear_checkbox_id(checkbox_name) 

563 context["widget"].update( 

564 { 

565 "checkbox_name": checkbox_name, 

566 "checkbox_id": checkbox_id, 

567 "is_initial": self.is_initial(value), 

568 "input_text": self.input_text, 

569 "initial_text": self.initial_text, 

570 "clear_checkbox_label": self.clear_checkbox_label, 

571 } 

572 ) 

573 context["widget"]["attrs"].setdefault("disabled", False) 

574 context["widget"]["attrs"]["checked"] = self.checked 

575 return context 

576 

577 def value_from_datadict(self, data, files, name): 

578 upload = super().value_from_datadict(data, files, name) 

579 self.checked = self.clear_checkbox_name(name) in data 

580 if not self.is_required and CheckboxInput().value_from_datadict( 

581 data, files, self.clear_checkbox_name(name) 

582 ): 

583 if upload: 

584 # If the user contradicts themselves (uploads a new file AND 

585 # checks the "clear" checkbox), we return a unique marker 

586 # object that FileField will turn into a ValidationError. 

587 return FILE_INPUT_CONTRADICTION 

588 # False signals to clear any existing value, as opposed to just None 

589 return False 

590 return upload 

591 

592 def value_omitted_from_data(self, data, files, name): 

593 return ( 

594 super().value_omitted_from_data(data, files, name) 

595 and self.clear_checkbox_name(name) not in data 

596 ) 

597 

598 

599class Textarea(Widget): 

600 template_name = "django/forms/widgets/textarea.html" 

601 

602 def __init__(self, attrs=None): 

603 # Use slightly better defaults than HTML's 20x2 box 

604 default_attrs = {"cols": "40", "rows": "10"} 

605 if attrs: 

606 default_attrs.update(attrs) 

607 super().__init__(default_attrs) 

608 

609 

610class DateTimeBaseInput(TextInput): 

611 format_key = "" 

612 supports_microseconds = False 

613 

614 def __init__(self, attrs=None, format=None): 

615 super().__init__(attrs) 

616 self.format = format or None 

617 

618 def format_value(self, value): 

619 return formats.localize_input( 

620 value, self.format or formats.get_format(self.format_key)[0] 

621 ) 

622 

623 

624class DateInput(DateTimeBaseInput): 

625 format_key = "DATE_INPUT_FORMATS" 

626 template_name = "django/forms/widgets/date.html" 

627 

628 

629class DateTimeInput(DateTimeBaseInput): 

630 format_key = "DATETIME_INPUT_FORMATS" 

631 template_name = "django/forms/widgets/datetime.html" 

632 

633 

634class TimeInput(DateTimeBaseInput): 

635 format_key = "TIME_INPUT_FORMATS" 

636 template_name = "django/forms/widgets/time.html" 

637 

638 

639# Defined at module level so that CheckboxInput is picklable (#17976) 

640def boolean_check(v): 

641 return not (v is False or v is None or v == "") 

642 

643 

644class CheckboxInput(Input): 

645 input_type = "checkbox" 

646 template_name = "django/forms/widgets/checkbox.html" 

647 

648 def __init__(self, attrs=None, check_test=None): 

649 super().__init__(attrs) 

650 # check_test is a callable that takes a value and returns True 

651 # if the checkbox should be checked for that value. 

652 self.check_test = boolean_check if check_test is None else check_test 

653 

654 def format_value(self, value): 

655 """Only return the 'value' attribute if value isn't empty.""" 

656 if value is True or value is False or value is None or value == "": 

657 return 

658 return str(value) 

659 

660 def get_context(self, name, value, attrs): 

661 if self.check_test(value): 

662 attrs = {**(attrs or {}), "checked": True} 

663 return super().get_context(name, value, attrs) 

664 

665 def value_from_datadict(self, data, files, name): 

666 if name not in data: 

667 # A missing value means False because HTML form submission does not 

668 # send results for unselected checkboxes. 

669 return False 

670 value = data.get(name) 

671 # Translate true and false strings to boolean values. 

672 values = {"true": True, "false": False} 

673 if isinstance(value, str): 

674 value = values.get(value.lower(), value) 

675 return bool(value) 

676 

677 def value_omitted_from_data(self, data, files, name): 

678 # HTML checkboxes don't appear in POST data if not checked, so it's 

679 # never known if the value is actually omitted. 

680 return False 

681 

682 

683class ChoiceWidget(Widget): 

684 allow_multiple_selected = False 

685 input_type = None 

686 template_name = None 

687 option_template_name = None 

688 add_id_index = True 

689 checked_attribute = {"checked": True} 

690 option_inherits_attrs = True 

691 

692 def __init__(self, attrs=None, choices=()): 

693 super().__init__(attrs) 

694 self.choices = choices 

695 

696 def __deepcopy__(self, memo): 

697 obj = copy.copy(self) 

698 obj.attrs = self.attrs.copy() 

699 obj.choices = copy.copy(self.choices) 

700 memo[id(self)] = obj 

701 return obj 

702 

703 def subwidgets(self, name, value, attrs=None): 

704 """ 

705 Yield all "subwidgets" of this widget. Used to enable iterating 

706 options from a BoundField for choice widgets. 

707 """ 

708 value = self.format_value(value) 

709 yield from self.options(name, value, attrs) 

710 

711 def options(self, name, value, attrs=None): 

712 """Yield a flat list of options for this widget.""" 

713 for group in self.optgroups(name, value, attrs): 

714 yield from group[1] 

715 

716 def optgroups(self, name, value, attrs=None): 

717 """Return a list of optgroups for this widget.""" 

718 groups = [] 

719 has_selected = False 

720 

721 for index, (option_value, option_label) in enumerate(self.choices): 

722 if option_value is None: 

723 option_value = "" 

724 

725 subgroup = [] 

726 if isinstance(option_label, (list, tuple)): 

727 group_name = option_value 

728 subindex = 0 

729 choices = option_label 

730 else: 

731 group_name = None 

732 subindex = None 

733 choices = [(option_value, option_label)] 

734 groups.append((group_name, subgroup, index)) 

735 

736 for subvalue, sublabel in choices: 

737 selected = (not has_selected or self.allow_multiple_selected) and str( 

738 subvalue 

739 ) in value 

740 has_selected |= selected 

741 subgroup.append( 

742 self.create_option( 

743 name, 

744 subvalue, 

745 sublabel, 

746 selected, 

747 index, 

748 subindex=subindex, 

749 attrs=attrs, 

750 ) 

751 ) 

752 if subindex is not None: 

753 subindex += 1 

754 return groups 

755 

756 def create_option( 

757 self, name, value, label, selected, index, subindex=None, attrs=None 

758 ): 

759 index = str(index) if subindex is None else "%s_%s" % (index, subindex) 

760 option_attrs = ( 

761 self.build_attrs(self.attrs, attrs) if self.option_inherits_attrs else {} 

762 ) 

763 if selected: 

764 option_attrs.update(self.checked_attribute) 

765 if "id" in option_attrs: 

766 option_attrs["id"] = self.id_for_label(option_attrs["id"], index) 

767 return { 

768 "name": name, 

769 "value": value, 

770 "label": label, 

771 "selected": selected, 

772 "index": index, 

773 "attrs": option_attrs, 

774 "type": self.input_type, 

775 "template_name": self.option_template_name, 

776 "wrap_label": True, 

777 } 

778 

779 def get_context(self, name, value, attrs): 

780 context = super().get_context(name, value, attrs) 

781 context["widget"]["optgroups"] = self.optgroups( 

782 name, context["widget"]["value"], attrs 

783 ) 

784 return context 

785 

786 def id_for_label(self, id_, index="0"): 

787 """ 

788 Use an incremented id for each option where the main widget 

789 references the zero index. 

790 """ 

791 if id_ and self.add_id_index: 

792 id_ = "%s_%s" % (id_, index) 

793 return id_ 

794 

795 def value_from_datadict(self, data, files, name): 

796 getter = data.get 

797 if self.allow_multiple_selected: 

798 try: 

799 getter = data.getlist 

800 except AttributeError: 

801 pass 

802 return getter(name) 

803 

804 def format_value(self, value): 

805 """Return selected values as a list.""" 

806 if value is None and self.allow_multiple_selected: 

807 return [] 

808 if not isinstance(value, (tuple, list)): 

809 value = [value] 

810 return [str(v) if v is not None else "" for v in value] 

811 

812 @property 

813 def choices(self): 

814 return self._choices 

815 

816 @choices.setter 

817 def choices(self, value): 

818 self._choices = normalize_choices(value) 

819 

820 

821class Select(ChoiceWidget): 

822 input_type = "select" 

823 template_name = "django/forms/widgets/select.html" 

824 option_template_name = "django/forms/widgets/select_option.html" 

825 add_id_index = False 

826 checked_attribute = {"selected": True} 

827 option_inherits_attrs = False 

828 

829 def get_context(self, name, value, attrs): 

830 context = super().get_context(name, value, attrs) 

831 if self.allow_multiple_selected: 

832 context["widget"]["attrs"]["multiple"] = True 

833 return context 

834 

835 @staticmethod 

836 def _choice_has_empty_value(choice): 

837 """Return True if the choice's value is empty string or None.""" 

838 value, _ = choice 

839 return value is None or value == "" 

840 

841 def use_required_attribute(self, initial): 

842 """ 

843 Don't render 'required' if the first <option> has a value, as that's 

844 invalid HTML. 

845 """ 

846 use_required_attribute = super().use_required_attribute(initial) 

847 # 'required' is always okay for <select multiple>. 

848 if self.allow_multiple_selected: 

849 return use_required_attribute 

850 

851 first_choice = next(iter(self.choices), None) 

852 return ( 

853 use_required_attribute 

854 and first_choice is not None 

855 and self._choice_has_empty_value(first_choice) 

856 ) 

857 

858 

859class NullBooleanSelect(Select): 

860 """ 

861 A Select Widget intended to be used with NullBooleanField. 

862 """ 

863 

864 def __init__(self, attrs=None): 

865 choices = ( 

866 ("unknown", _("Unknown")), 

867 ("true", _("Yes")), 

868 ("false", _("No")), 

869 ) 

870 super().__init__(attrs, choices) 

871 

872 def format_value(self, value): 

873 try: 

874 return { 

875 True: "true", 

876 False: "false", 

877 "true": "true", 

878 "false": "false", 

879 # For backwards compatibility with Django < 2.2. 

880 "2": "true", 

881 "3": "false", 

882 }[value] 

883 except KeyError: 

884 return "unknown" 

885 

886 def value_from_datadict(self, data, files, name): 

887 value = data.get(name) 

888 return { 

889 True: True, 

890 "True": True, 

891 "False": False, 

892 False: False, 

893 "true": True, 

894 "false": False, 

895 # For backwards compatibility with Django < 2.2. 

896 "2": True, 

897 "3": False, 

898 }.get(value) 

899 

900 

901class SelectMultiple(Select): 

902 allow_multiple_selected = True 

903 

904 def value_from_datadict(self, data, files, name): 

905 try: 

906 getter = data.getlist 

907 except AttributeError: 

908 getter = data.get 

909 return getter(name) 

910 

911 def value_omitted_from_data(self, data, files, name): 

912 # An unselected <select multiple> doesn't appear in POST data, so it's 

913 # never known if the value is actually omitted. 

914 return False 

915 

916 

917class RadioSelect(ChoiceWidget): 

918 input_type = "radio" 

919 template_name = "django/forms/widgets/radio.html" 

920 option_template_name = "django/forms/widgets/radio_option.html" 

921 use_fieldset = True 

922 

923 def id_for_label(self, id_, index=None): 

924 """ 

925 Don't include for="field_0" in <label> to improve accessibility when 

926 using a screen reader, in addition clicking such a label would toggle 

927 the first input. 

928 """ 

929 if index is None: 

930 return "" 

931 return super().id_for_label(id_, index) 

932 

933 

934class CheckboxSelectMultiple(RadioSelect): 

935 allow_multiple_selected = True 

936 input_type = "checkbox" 

937 template_name = "django/forms/widgets/checkbox_select.html" 

938 option_template_name = "django/forms/widgets/checkbox_option.html" 

939 

940 def use_required_attribute(self, initial): 

941 # Don't use the 'required' attribute because browser validation would 

942 # require all checkboxes to be checked instead of at least one. 

943 return False 

944 

945 def value_omitted_from_data(self, data, files, name): 

946 # HTML checkboxes don't appear in POST data if not checked, so it's 

947 # never known if the value is actually omitted. 

948 return False 

949 

950 

951class MultiWidget(Widget): 

952 """ 

953 A widget that is composed of multiple widgets. 

954 

955 In addition to the values added by Widget.get_context(), this widget 

956 adds a list of subwidgets to the context as widget['subwidgets']. 

957 These can be looped over and rendered like normal widgets. 

958 

959 You'll probably want to use this class with MultiValueField. 

960 """ 

961 

962 template_name = "django/forms/widgets/multiwidget.html" 

963 use_fieldset = True 

964 

965 def __init__(self, widgets, attrs=None): 

966 if isinstance(widgets, dict): 

967 self.widgets_names = [("_%s" % name) if name else "" for name in widgets] 

968 widgets = widgets.values() 

969 else: 

970 self.widgets_names = ["_%s" % i for i in range(len(widgets))] 

971 self.widgets = [w() if isinstance(w, type) else w for w in widgets] 

972 super().__init__(attrs) 

973 

974 @property 

975 def is_hidden(self): 

976 return all(w.is_hidden for w in self.widgets) 

977 

978 def get_context(self, name, value, attrs): 

979 context = super().get_context(name, value, attrs) 

980 if self.is_localized: 

981 for widget in self.widgets: 

982 widget.is_localized = self.is_localized 

983 # value is a list/tuple of values, each corresponding to a widget 

984 # in self.widgets. 

985 if not isinstance(value, (list, tuple)): 

986 value = self.decompress(value) 

987 

988 final_attrs = context["widget"]["attrs"] 

989 input_type = final_attrs.pop("type", None) 

990 id_ = final_attrs.get("id") 

991 subwidgets = [] 

992 for i, (widget_name, widget) in enumerate( 

993 zip(self.widgets_names, self.widgets) 

994 ): 

995 if input_type is not None: 

996 widget.input_type = input_type 

997 widget_name = name + widget_name 

998 try: 

999 widget_value = value[i] 

1000 except IndexError: 

1001 widget_value = None 

1002 if id_: 

1003 widget_attrs = final_attrs.copy() 

1004 widget_attrs["id"] = "%s_%s" % (id_, i) 

1005 else: 

1006 widget_attrs = final_attrs 

1007 subwidgets.append( 

1008 widget.get_context(widget_name, widget_value, widget_attrs)["widget"] 

1009 ) 

1010 context["widget"]["subwidgets"] = subwidgets 

1011 return context 

1012 

1013 def id_for_label(self, id_): 

1014 return "" 

1015 

1016 def value_from_datadict(self, data, files, name): 

1017 return [ 

1018 widget.value_from_datadict(data, files, name + widget_name) 

1019 for widget_name, widget in zip(self.widgets_names, self.widgets) 

1020 ] 

1021 

1022 def value_omitted_from_data(self, data, files, name): 

1023 return all( 

1024 widget.value_omitted_from_data(data, files, name + widget_name) 

1025 for widget_name, widget in zip(self.widgets_names, self.widgets) 

1026 ) 

1027 

1028 def decompress(self, value): 

1029 """ 

1030 Return a list of decompressed values for the given compressed value. 

1031 The given value can be assumed to be valid, but not necessarily 

1032 non-empty. 

1033 """ 

1034 raise NotImplementedError("Subclasses must implement this method.") 

1035 

1036 def _get_media(self): 

1037 """ 

1038 Media for a multiwidget is the combination of all media of the 

1039 subwidgets. 

1040 """ 

1041 media = Media() 

1042 for w in self.widgets: 

1043 media += w.media 

1044 return media 

1045 

1046 media = property(_get_media) 

1047 

1048 def __deepcopy__(self, memo): 

1049 obj = super().__deepcopy__(memo) 

1050 obj.widgets = copy.deepcopy(self.widgets) 

1051 return obj 

1052 

1053 @property 

1054 def needs_multipart_form(self): 

1055 return any(w.needs_multipart_form for w in self.widgets) 

1056 

1057 

1058class SplitDateTimeWidget(MultiWidget): 

1059 """ 

1060 A widget that splits datetime input into two <input type="text"> boxes. 

1061 """ 

1062 

1063 supports_microseconds = False 

1064 template_name = "django/forms/widgets/splitdatetime.html" 

1065 

1066 def __init__( 

1067 self, 

1068 attrs=None, 

1069 date_format=None, 

1070 time_format=None, 

1071 date_attrs=None, 

1072 time_attrs=None, 

1073 ): 

1074 widgets = ( 

1075 DateInput( 

1076 attrs=attrs if date_attrs is None else date_attrs, 

1077 format=date_format, 

1078 ), 

1079 TimeInput( 

1080 attrs=attrs if time_attrs is None else time_attrs, 

1081 format=time_format, 

1082 ), 

1083 ) 

1084 super().__init__(widgets) 

1085 

1086 def decompress(self, value): 

1087 if value: 

1088 value = to_current_timezone(value) 

1089 return [value.date(), value.time()] 

1090 return [None, None] 

1091 

1092 

1093class SplitHiddenDateTimeWidget(SplitDateTimeWidget): 

1094 """ 

1095 A widget that splits datetime input into two <input type="hidden"> inputs. 

1096 """ 

1097 

1098 template_name = "django/forms/widgets/splithiddendatetime.html" 

1099 

1100 def __init__( 

1101 self, 

1102 attrs=None, 

1103 date_format=None, 

1104 time_format=None, 

1105 date_attrs=None, 

1106 time_attrs=None, 

1107 ): 

1108 super().__init__(attrs, date_format, time_format, date_attrs, time_attrs) 

1109 for widget in self.widgets: 

1110 widget.input_type = "hidden" 

1111 

1112 

1113class SelectDateWidget(Widget): 

1114 """ 

1115 A widget that splits date input into three <select> boxes. 

1116 

1117 This also serves as an example of a Widget that has more than one HTML 

1118 element and hence implements value_from_datadict. 

1119 """ 

1120 

1121 none_value = ("", "---") 

1122 month_field = "%s_month" 

1123 day_field = "%s_day" 

1124 year_field = "%s_year" 

1125 template_name = "django/forms/widgets/select_date.html" 

1126 input_type = "select" 

1127 select_widget = Select 

1128 date_re = _lazy_re_compile(r"(\d{4}|0)-(\d\d?)-(\d\d?)$") 

1129 use_fieldset = True 

1130 

1131 def __init__(self, attrs=None, years=None, months=None, empty_label=None): 

1132 self.attrs = attrs or {} 

1133 

1134 # Optional list or tuple of years to use in the "year" select box. 

1135 if years: 

1136 self.years = years 

1137 else: 

1138 this_year = datetime.date.today().year 

1139 self.years = range(this_year, this_year + 10) 

1140 

1141 # Optional dict of months to use in the "month" select box. 

1142 if months: 

1143 self.months = months 

1144 else: 

1145 self.months = MONTHS 

1146 

1147 # Optional string, list, or tuple to use as empty_label. 

1148 if isinstance(empty_label, (list, tuple)): 

1149 if not len(empty_label) == 3: 

1150 raise ValueError("empty_label list/tuple must have 3 elements.") 

1151 

1152 self.year_none_value = ("", empty_label[0]) 

1153 self.month_none_value = ("", empty_label[1]) 

1154 self.day_none_value = ("", empty_label[2]) 

1155 else: 

1156 if empty_label is not None: 

1157 self.none_value = ("", empty_label) 

1158 

1159 self.year_none_value = self.none_value 

1160 self.month_none_value = self.none_value 

1161 self.day_none_value = self.none_value 

1162 

1163 def get_context(self, name, value, attrs): 

1164 context = super().get_context(name, value, attrs) 

1165 date_context = {} 

1166 year_choices = [(i, str(i)) for i in self.years] 

1167 if not self.is_required: 

1168 year_choices.insert(0, self.year_none_value) 

1169 year_name = self.year_field % name 

1170 date_context["year"] = self.select_widget( 

1171 attrs, choices=year_choices 

1172 ).get_context( 

1173 name=year_name, 

1174 value=context["widget"]["value"]["year"], 

1175 attrs={**context["widget"]["attrs"], "id": "id_%s" % year_name}, 

1176 ) 

1177 month_choices = list(self.months.items()) 

1178 if not self.is_required: 

1179 month_choices.insert(0, self.month_none_value) 

1180 month_name = self.month_field % name 

1181 date_context["month"] = self.select_widget( 

1182 attrs, choices=month_choices 

1183 ).get_context( 

1184 name=month_name, 

1185 value=context["widget"]["value"]["month"], 

1186 attrs={**context["widget"]["attrs"], "id": "id_%s" % month_name}, 

1187 ) 

1188 day_choices = [(i, i) for i in range(1, 32)] 

1189 if not self.is_required: 

1190 day_choices.insert(0, self.day_none_value) 

1191 day_name = self.day_field % name 

1192 date_context["day"] = self.select_widget( 

1193 attrs, 

1194 choices=day_choices, 

1195 ).get_context( 

1196 name=day_name, 

1197 value=context["widget"]["value"]["day"], 

1198 attrs={**context["widget"]["attrs"], "id": "id_%s" % day_name}, 

1199 ) 

1200 subwidgets = [] 

1201 for field in self._parse_date_fmt(): 

1202 subwidgets.append(date_context[field]["widget"]) 

1203 context["widget"]["subwidgets"] = subwidgets 

1204 return context 

1205 

1206 def format_value(self, value): 

1207 """ 

1208 Return a dict containing the year, month, and day of the current value. 

1209 Use dict instead of a datetime to allow invalid dates such as February 

1210 31 to display correctly. 

1211 """ 

1212 year, month, day = None, None, None 

1213 if isinstance(value, (datetime.date, datetime.datetime)): 

1214 year, month, day = value.year, value.month, value.day 

1215 elif isinstance(value, str): 

1216 match = self.date_re.match(value) 

1217 if match: 

1218 # Convert any zeros in the date to empty strings to match the 

1219 # empty option value. 

1220 year, month, day = [int(val) or "" for val in match.groups()] 

1221 else: 

1222 input_format = get_format("DATE_INPUT_FORMATS")[0] 

1223 try: 

1224 d = datetime.datetime.strptime(value, input_format) 

1225 except ValueError: 

1226 pass 

1227 else: 

1228 year, month, day = d.year, d.month, d.day 

1229 return {"year": year, "month": month, "day": day} 

1230 

1231 @staticmethod 

1232 def _parse_date_fmt(): 

1233 fmt = get_format("DATE_FORMAT") 

1234 escaped = False 

1235 for char in fmt: 

1236 if escaped: 

1237 escaped = False 

1238 elif char == "\\": 

1239 escaped = True 

1240 elif char in "Yy": 

1241 yield "year" 

1242 elif char in "bEFMmNn": 

1243 yield "month" 

1244 elif char in "dj": 

1245 yield "day" 

1246 

1247 def id_for_label(self, id_): 

1248 for first_select in self._parse_date_fmt(): 

1249 return "%s_%s" % (id_, first_select) 

1250 return "%s_month" % id_ 

1251 

1252 def value_from_datadict(self, data, files, name): 

1253 y = data.get(self.year_field % name) 

1254 m = data.get(self.month_field % name) 

1255 d = data.get(self.day_field % name) 

1256 if y == m == d == "": 

1257 return None 

1258 if y is not None and m is not None and d is not None: 

1259 input_format = get_format("DATE_INPUT_FORMATS")[0] 

1260 input_format = formats.sanitize_strftime_format(input_format) 

1261 try: 

1262 date_value = datetime.date(int(y), int(m), int(d)) 

1263 except ValueError: 

1264 # Return pseudo-ISO dates with zeros for any unselected values, 

1265 # e.g. '2017-0-23'. 

1266 return "%s-%s-%s" % (y or 0, m or 0, d or 0) 

1267 except OverflowError: 

1268 return "0-0-0" 

1269 return date_value.strftime(input_format) 

1270 return data.get(name) 

1271 

1272 def value_omitted_from_data(self, data, files, name): 

1273 return not any( 

1274 ("{}_{}".format(name, interval) in data) 

1275 for interval in ("year", "month", "day") 

1276 )