Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/django/forms/widgets.py: 37%

627 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

1""" 

2HTML Widget classes 

3""" 

4 

5import copy 

6import datetime 

7import warnings 

8from collections import defaultdict 

9from itertools import chain 

10 

11from django.forms.utils import to_current_timezone 

12from django.templatetags.static import static 

13from django.utils import formats 

14from django.utils.datastructures import OrderedSet 

15from django.utils.dates import MONTHS 

16from django.utils.formats import get_format 

17from django.utils.html import format_html, html_safe 

18from django.utils.regex_helper import _lazy_re_compile 

19from django.utils.safestring import mark_safe 

20from django.utils.topological_sort import CyclicDependencyError, stable_topological_sort 

21from django.utils.translation import gettext_lazy as _ 

22 

23from .renderers import get_default_renderer 

24 

25__all__ = ( 

26 "Media", 

27 "MediaDefiningClass", 

28 "Widget", 

29 "TextInput", 

30 "NumberInput", 

31 "EmailInput", 

32 "URLInput", 

33 "PasswordInput", 

34 "HiddenInput", 

35 "MultipleHiddenInput", 

36 "FileInput", 

37 "ClearableFileInput", 

38 "Textarea", 

39 "DateInput", 

40 "DateTimeInput", 

41 "TimeInput", 

42 "CheckboxInput", 

43 "Select", 

44 "NullBooleanSelect", 

45 "SelectMultiple", 

46 "RadioSelect", 

47 "CheckboxSelectMultiple", 

48 "MultiWidget", 

49 "SplitDateTimeWidget", 

50 "SplitHiddenDateTimeWidget", 

51 "SelectDateWidget", 

52) 

53 

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

55 

56 

57class MediaOrderConflictWarning(RuntimeWarning): 

58 pass 

59 

60 

61@html_safe 

62class Media: 

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

64 if media is not None: 64 ↛ 65line 64 didn't jump to line 65, because the condition on line 64 was never true

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

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

67 else: 

68 if css is None: 68 ↛ 69line 68 didn't jump to line 69, because the condition on line 68 was never true

69 css = {} 

70 if js is None: 70 ↛ 71line 70 didn't jump to line 71, because the condition on line 70 was never true

71 js = [] 

72 self._css_lists = [css] 

73 self._js_lists = [js] 

74 

75 def __repr__(self): 

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

77 

78 def __str__(self): 

79 return self.render() 

80 

81 @property 

82 def _css(self): 

83 css = defaultdict(list) 

84 for css_list in self._css_lists: 

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

86 css[medium].append(sublist) 

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

88 

89 @property 

90 def _js(self): 

91 return self.merge(*self._js_lists) 

92 

93 def render(self): 

94 return mark_safe( 

95 "\n".join( 

96 chain.from_iterable( 

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

98 ) 

99 ) 

100 ) 

101 

102 def render_js(self): 

103 return [ 

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

105 for path in self._js 

106 ] 

107 

108 def render_css(self): 

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

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

111 media = sorted(self._css) 

112 return chain.from_iterable( 

113 [ 

114 format_html( 

115 '<link href="{}" type="text/css" media="{}" rel="stylesheet">', 

116 self.absolute_path(path), 

117 medium, 

118 ) 

119 for path in self._css[medium] 

120 ] 

121 for medium in media 

122 ) 

123 

124 def absolute_path(self, path): 

125 """ 

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

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

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

129 """ 

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

131 return path 

132 return static(path) 

133 

134 def __getitem__(self, name): 

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

136 if name in MEDIA_TYPES: 

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

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

139 

140 @staticmethod 

141 def merge(*lists): 

142 """ 

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

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

145 

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

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

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

149 """ 

150 dependency_graph = defaultdict(set) 

151 all_items = OrderedSet() 

152 for list_ in filter(None, lists): 

153 head = list_[0] 

154 # The first items depend on nothing but have to be part of the 

155 # dependency graph to be included in the result. 

156 dependency_graph.setdefault(head, set()) 

157 for item in list_: 

158 all_items.add(item) 

159 # No self dependencies 

160 if head != item: 

161 dependency_graph[item].add(head) 

162 head = item 

163 try: 

164 return stable_topological_sort(all_items, dependency_graph) 

165 except CyclicDependencyError: 

166 warnings.warn( 

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

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

169 ), 

170 MediaOrderConflictWarning, 

171 ) 

172 return list(all_items) 

173 

174 def __add__(self, other): 

175 combined = Media() 

176 combined._css_lists = self._css_lists[:] 

177 combined._js_lists = self._js_lists[:] 

178 for item in other._css_lists: 

179 if item and item not in self._css_lists: 

180 combined._css_lists.append(item) 

181 for item in other._js_lists: 

182 if item and item not in self._js_lists: 

183 combined._js_lists.append(item) 

184 return combined 

185 

186 

187def media_property(cls): 

188 def _media(self): 

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

190 sup_cls = super(cls, self) 

191 try: 

192 base = sup_cls.media 

193 except AttributeError: 

194 base = Media() 

195 

196 # Get the media definition for this class 

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

198 if definition: 

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

200 if extend: 

201 if extend is True: 

202 m = base 

203 else: 

204 m = Media() 

205 for medium in extend: 

206 m = m + base[medium] 

207 return m + Media(definition) 

208 return Media(definition) 

209 return base 

210 

211 return property(_media) 

212 

213 

214class MediaDefiningClass(type): 

215 """ 

216 Metaclass for classes that can have media definitions. 

217 """ 

218 

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

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

221 

222 if "media" not in attrs: 

223 new_class.media = media_property(new_class) 

224 

225 return new_class 

226 

227 

228class Widget(metaclass=MediaDefiningClass): 

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

230 is_localized = False 

231 is_required = False 

232 supports_microseconds = True 

233 

234 def __init__(self, attrs=None): 

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

236 

237 def __deepcopy__(self, memo): 

238 obj = copy.copy(self) 

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

240 memo[id(self)] = obj 

241 return obj 

242 

243 @property 

244 def is_hidden(self): 

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

246 

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

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

249 yield context["widget"] 

250 

251 def format_value(self, value): 

252 """ 

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

254 """ 

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

256 return None 

257 if self.is_localized: 

258 return formats.localize_input(value) 

259 return str(value) 

260 

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

262 return { 

263 "widget": { 

264 "name": name, 

265 "is_hidden": self.is_hidden, 

266 "required": self.is_required, 

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

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

269 "template_name": self.template_name, 

270 }, 

271 } 

272 

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

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

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

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

277 

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

279 if renderer is None: 

280 renderer = get_default_renderer() 

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

282 

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

284 """Build an attribute dictionary.""" 

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

286 

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

288 """ 

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

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

291 """ 

292 return data.get(name) 

293 

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

295 return name not in data 

296 

297 def id_for_label(self, id_): 

298 """ 

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

300 given the ID of the field. Return None if no ID is available. 

301 

302 This hook is necessary because some widgets have multiple HTML 

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

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

305 tags. 

306 """ 

307 return id_ 

308 

309 def use_required_attribute(self, initial): 

310 return not self.is_hidden 

311 

312 

313class Input(Widget): 

314 """ 

315 Base class for all <input> widgets. 

316 """ 

317 

318 input_type = None # Subclasses must define this. 

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

320 

321 def __init__(self, attrs=None): 

322 if attrs is not None: 

323 attrs = attrs.copy() 

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

325 super().__init__(attrs) 

326 

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

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

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

330 return context 

331 

332 

333class TextInput(Input): 

334 input_type = "text" 

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

336 

337 

338class NumberInput(Input): 

339 input_type = "number" 

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

341 

342 

343class EmailInput(Input): 

344 input_type = "email" 

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

346 

347 

348class URLInput(Input): 

349 input_type = "url" 

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

351 

352 

353class PasswordInput(Input): 

354 input_type = "password" 

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

356 

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

358 super().__init__(attrs) 

359 self.render_value = render_value 

360 

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

362 if not self.render_value: 

363 value = None 

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

365 

366 

367class HiddenInput(Input): 

368 input_type = "hidden" 

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

370 

371 

372class MultipleHiddenInput(HiddenInput): 

373 """ 

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

375 of values. 

376 """ 

377 

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

379 

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

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

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

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

384 

385 subwidgets = [] 

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

387 widget_attrs = final_attrs.copy() 

388 if id_: 

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

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

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

392 widget = HiddenInput() 

393 widget.is_required = self.is_required 

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

395 

396 context["widget"]["subwidgets"] = subwidgets 

397 return context 

398 

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

400 try: 

401 getter = data.getlist 

402 except AttributeError: 

403 getter = data.get 

404 return getter(name) 

405 

406 def format_value(self, value): 

407 return [] if value is None else value 

408 

409 

410class FileInput(Input): 

411 input_type = "file" 

412 needs_multipart_form = True 

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

414 

415 def format_value(self, value): 

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

417 return 

418 

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

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

421 return files.get(name) 

422 

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

424 return name not in files 

425 

426 def use_required_attribute(self, initial): 

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

428 

429 

430FILE_INPUT_CONTRADICTION = object() 

431 

432 

433class ClearableFileInput(FileInput): 

434 clear_checkbox_label = _("Clear") 

435 initial_text = _("Currently") 

436 input_text = _("Change") 

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

438 

439 def clear_checkbox_name(self, name): 

440 """ 

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

442 input. 

443 """ 

444 return name + "-clear" 

445 

446 def clear_checkbox_id(self, name): 

447 """ 

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

449 """ 

450 return name + "_id" 

451 

452 def is_initial(self, value): 

453 """ 

454 Return whether value is considered to be initial value. 

455 """ 

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

457 

458 def format_value(self, value): 

459 """ 

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

461 """ 

462 if self.is_initial(value): 

463 return value 

464 

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

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

467 checkbox_name = self.clear_checkbox_name(name) 

468 checkbox_id = self.clear_checkbox_id(checkbox_name) 

469 context["widget"].update( 

470 { 

471 "checkbox_name": checkbox_name, 

472 "checkbox_id": checkbox_id, 

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

474 "input_text": self.input_text, 

475 "initial_text": self.initial_text, 

476 "clear_checkbox_label": self.clear_checkbox_label, 

477 } 

478 ) 

479 return context 

480 

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

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

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

484 data, files, self.clear_checkbox_name(name) 

485 ): 

486 

487 if upload: 

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

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

490 # object that FileField will turn into a ValidationError. 

491 return FILE_INPUT_CONTRADICTION 

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

493 return False 

494 return upload 

495 

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

497 return ( 

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

499 and self.clear_checkbox_name(name) not in data 

500 ) 

501 

502 

503class Textarea(Widget): 

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

505 

506 def __init__(self, attrs=None): 

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

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

509 if attrs: 

510 default_attrs.update(attrs) 

511 super().__init__(default_attrs) 

512 

513 

514class DateTimeBaseInput(TextInput): 

515 format_key = "" 

516 supports_microseconds = False 

517 

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

519 super().__init__(attrs) 

520 self.format = format or None 

521 

522 def format_value(self, value): 

523 return formats.localize_input( 

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

525 ) 

526 

527 

528class DateInput(DateTimeBaseInput): 

529 format_key = "DATE_INPUT_FORMATS" 

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

531 

532 

533class DateTimeInput(DateTimeBaseInput): 

534 format_key = "DATETIME_INPUT_FORMATS" 

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

536 

537 

538class TimeInput(DateTimeBaseInput): 

539 format_key = "TIME_INPUT_FORMATS" 

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

541 

542 

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

544def boolean_check(v): 

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

546 

547 

548class CheckboxInput(Input): 

549 input_type = "checkbox" 

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

551 

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

553 super().__init__(attrs) 

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

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

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

557 

558 def format_value(self, value): 

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

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

561 return 

562 return str(value) 

563 

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

565 if self.check_test(value): 

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

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

568 

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

570 if name not in data: 

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

572 # send results for unselected checkboxes. 

573 return False 

574 value = data.get(name) 

575 # Translate true and false strings to boolean values. 

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

577 if isinstance(value, str): 

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

579 return bool(value) 

580 

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

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

583 # never known if the value is actually omitted. 

584 return False 

585 

586 

587class ChoiceWidget(Widget): 

588 allow_multiple_selected = False 

589 input_type = None 

590 template_name = None 

591 option_template_name = None 

592 add_id_index = True 

593 checked_attribute = {"checked": True} 

594 option_inherits_attrs = True 

595 

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

597 super().__init__(attrs) 

598 # choices can be any iterable, but we may need to render this widget 

599 # multiple times. Thus, collapse it into a list so it can be consumed 

600 # more than once. 

601 self.choices = list(choices) 

602 

603 def __deepcopy__(self, memo): 

604 obj = copy.copy(self) 

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

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

607 memo[id(self)] = obj 

608 return obj 

609 

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

611 """ 

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

613 options from a BoundField for choice widgets. 

614 """ 

615 value = self.format_value(value) 

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

617 

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

619 """Yield a flat list of options for this widgets.""" 

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

621 yield from group[1] 

622 

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

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

625 groups = [] 

626 has_selected = False 

627 

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

629 if option_value is None: 

630 option_value = "" 

631 

632 subgroup = [] 

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

634 group_name = option_value 

635 subindex = 0 

636 choices = option_label 

637 else: 

638 group_name = None 

639 subindex = None 

640 choices = [(option_value, option_label)] 

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

642 

643 for subvalue, sublabel in choices: 

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

645 subvalue 

646 ) in value 

647 has_selected |= selected 

648 subgroup.append( 

649 self.create_option( 

650 name, 

651 subvalue, 

652 sublabel, 

653 selected, 

654 index, 

655 subindex=subindex, 

656 attrs=attrs, 

657 ) 

658 ) 

659 if subindex is not None: 

660 subindex += 1 

661 return groups 

662 

663 def create_option( 

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

665 ): 

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

667 option_attrs = ( 

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

669 ) 

670 if selected: 

671 option_attrs.update(self.checked_attribute) 

672 if "id" in option_attrs: 

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

674 return { 

675 "name": name, 

676 "value": value, 

677 "label": label, 

678 "selected": selected, 

679 "index": index, 

680 "attrs": option_attrs, 

681 "type": self.input_type, 

682 "template_name": self.option_template_name, 

683 "wrap_label": True, 

684 } 

685 

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

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

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

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

690 ) 

691 return context 

692 

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

694 """ 

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

696 references the zero index. 

697 """ 

698 if id_ and self.add_id_index: 

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

700 return id_ 

701 

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

703 getter = data.get 

704 if self.allow_multiple_selected: 704 ↛ 705line 704 didn't jump to line 705, because the condition on line 704 was never true

705 try: 

706 getter = data.getlist 

707 except AttributeError: 

708 pass 

709 return getter(name) 

710 

711 def format_value(self, value): 

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

713 if value is None and self.allow_multiple_selected: 

714 return [] 

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

716 value = [value] 

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

718 

719 

720class Select(ChoiceWidget): 

721 input_type = "select" 

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

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

724 add_id_index = False 

725 checked_attribute = {"selected": True} 

726 option_inherits_attrs = False 

727 

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

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

730 if self.allow_multiple_selected: 

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

732 return context 

733 

734 @staticmethod 

735 def _choice_has_empty_value(choice): 

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

737 value, _ = choice 

738 return value is None or value == "" 

739 

740 def use_required_attribute(self, initial): 

741 """ 

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

743 invalid HTML. 

744 """ 

745 use_required_attribute = super().use_required_attribute(initial) 

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

747 if self.allow_multiple_selected: 

748 return use_required_attribute 

749 

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

751 return ( 

752 use_required_attribute 

753 and first_choice is not None 

754 and self._choice_has_empty_value(first_choice) 

755 ) 

756 

757 

758class NullBooleanSelect(Select): 

759 """ 

760 A Select Widget intended to be used with NullBooleanField. 

761 """ 

762 

763 def __init__(self, attrs=None): 

764 choices = ( 

765 ("unknown", _("Unknown")), 

766 ("true", _("Yes")), 

767 ("false", _("No")), 

768 ) 

769 super().__init__(attrs, choices) 

770 

771 def format_value(self, value): 

772 try: 

773 return { 

774 True: "true", 

775 False: "false", 

776 "true": "true", 

777 "false": "false", 

778 # For backwards compatibility with Django < 2.2. 

779 "2": "true", 

780 "3": "false", 

781 }[value] 

782 except KeyError: 

783 return "unknown" 

784 

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

786 value = data.get(name) 

787 return { 

788 True: True, 

789 "True": True, 

790 "False": False, 

791 False: False, 

792 "true": True, 

793 "false": False, 

794 # For backwards compatibility with Django < 2.2. 

795 "2": True, 

796 "3": False, 

797 }.get(value) 

798 

799 

800class SelectMultiple(Select): 

801 allow_multiple_selected = True 

802 

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

804 try: 

805 getter = data.getlist 

806 except AttributeError: 

807 getter = data.get 

808 return getter(name) 

809 

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

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

812 # never known if the value is actually omitted. 

813 return False 

814 

815 

816class RadioSelect(ChoiceWidget): 

817 input_type = "radio" 

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

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

820 

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

822 """ 

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

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

825 the first input. 

826 """ 

827 if index is None: 

828 return "" 

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

830 

831 

832class CheckboxSelectMultiple(RadioSelect): 

833 allow_multiple_selected = True 

834 input_type = "checkbox" 

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

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

837 

838 def use_required_attribute(self, initial): 

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

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

841 return False 

842 

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

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

845 # never known if the value is actually omitted. 

846 return False 

847 

848 

849class MultiWidget(Widget): 

850 """ 

851 A widget that is composed of multiple widgets. 

852 

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

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

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

856 

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

858 """ 

859 

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

861 

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

863 if isinstance(widgets, dict): 

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

865 widgets = widgets.values() 

866 else: 

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

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

869 super().__init__(attrs) 

870 

871 @property 

872 def is_hidden(self): 

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

874 

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

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

877 if self.is_localized: 

878 for widget in self.widgets: 

879 widget.is_localized = self.is_localized 

880 # value is a list of values, each corresponding to a widget 

881 # in self.widgets. 

882 if not isinstance(value, list): 

883 value = self.decompress(value) 

884 

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

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

887 id_ = final_attrs.get("id") 

888 subwidgets = [] 

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

890 zip(self.widgets_names, self.widgets) 

891 ): 

892 if input_type is not None: 

893 widget.input_type = input_type 

894 widget_name = name + widget_name 

895 try: 

896 widget_value = value[i] 

897 except IndexError: 

898 widget_value = None 

899 if id_: 

900 widget_attrs = final_attrs.copy() 

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

902 else: 

903 widget_attrs = final_attrs 

904 subwidgets.append( 

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

906 ) 

907 context["widget"]["subwidgets"] = subwidgets 

908 return context 

909 

910 def id_for_label(self, id_): 

911 if id_: 

912 id_ += "_0" 

913 return id_ 

914 

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

916 return [ 

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

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

919 ] 

920 

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

922 return all( 

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

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

925 ) 

926 

927 def decompress(self, value): 

928 """ 

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

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

931 non-empty. 

932 """ 

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

934 

935 def _get_media(self): 

936 """ 

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

938 subwidgets. 

939 """ 

940 media = Media() 

941 for w in self.widgets: 

942 media = media + w.media 

943 return media 

944 

945 media = property(_get_media) 

946 

947 def __deepcopy__(self, memo): 

948 obj = super().__deepcopy__(memo) 

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

950 return obj 

951 

952 @property 

953 def needs_multipart_form(self): 

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

955 

956 

957class SplitDateTimeWidget(MultiWidget): 

958 """ 

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

960 """ 

961 

962 supports_microseconds = False 

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

964 

965 def __init__( 

966 self, 

967 attrs=None, 

968 date_format=None, 

969 time_format=None, 

970 date_attrs=None, 

971 time_attrs=None, 

972 ): 

973 widgets = ( 

974 DateInput( 

975 attrs=attrs if date_attrs is None else date_attrs, 

976 format=date_format, 

977 ), 

978 TimeInput( 

979 attrs=attrs if time_attrs is None else time_attrs, 

980 format=time_format, 

981 ), 

982 ) 

983 super().__init__(widgets) 

984 

985 def decompress(self, value): 

986 if value: 

987 value = to_current_timezone(value) 

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

989 return [None, None] 

990 

991 

992class SplitHiddenDateTimeWidget(SplitDateTimeWidget): 

993 """ 

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

995 """ 

996 

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

998 

999 def __init__( 

1000 self, 

1001 attrs=None, 

1002 date_format=None, 

1003 time_format=None, 

1004 date_attrs=None, 

1005 time_attrs=None, 

1006 ): 

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

1008 for widget in self.widgets: 

1009 widget.input_type = "hidden" 

1010 

1011 

1012class SelectDateWidget(Widget): 

1013 """ 

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

1015 

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

1017 element and hence implements value_from_datadict. 

1018 """ 

1019 

1020 none_value = ("", "---") 

1021 month_field = "%s_month" 

1022 day_field = "%s_day" 

1023 year_field = "%s_year" 

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

1025 input_type = "select" 

1026 select_widget = Select 

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

1028 

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

1030 self.attrs = attrs or {} 

1031 

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

1033 if years: 

1034 self.years = years 

1035 else: 

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

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

1038 

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

1040 if months: 

1041 self.months = months 

1042 else: 

1043 self.months = MONTHS 

1044 

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

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

1047 if not len(empty_label) == 3: 

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

1049 

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

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

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

1053 else: 

1054 if empty_label is not None: 

1055 self.none_value = ("", empty_label) 

1056 

1057 self.year_none_value = self.none_value 

1058 self.month_none_value = self.none_value 

1059 self.day_none_value = self.none_value 

1060 

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

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

1063 date_context = {} 

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

1065 if not self.is_required: 

1066 year_choices.insert(0, self.year_none_value) 

1067 year_name = self.year_field % name 

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

1069 attrs, choices=year_choices 

1070 ).get_context( 

1071 name=year_name, 

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

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

1074 ) 

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

1076 if not self.is_required: 

1077 month_choices.insert(0, self.month_none_value) 

1078 month_name = self.month_field % name 

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

1080 attrs, choices=month_choices 

1081 ).get_context( 

1082 name=month_name, 

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

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

1085 ) 

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

1087 if not self.is_required: 

1088 day_choices.insert(0, self.day_none_value) 

1089 day_name = self.day_field % name 

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

1091 attrs, 

1092 choices=day_choices, 

1093 ).get_context( 

1094 name=day_name, 

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

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

1097 ) 

1098 subwidgets = [] 

1099 for field in self._parse_date_fmt(): 

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

1101 context["widget"]["subwidgets"] = subwidgets 

1102 return context 

1103 

1104 def format_value(self, value): 

1105 """ 

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

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

1108 31 to display correctly. 

1109 """ 

1110 year, month, day = None, None, None 

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

1112 year, month, day = value.year, value.month, value.day 

1113 elif isinstance(value, str): 

1114 match = self.date_re.match(value) 

1115 if match: 

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

1117 # empty option value. 

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

1119 else: 

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

1121 try: 

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

1123 except ValueError: 

1124 pass 

1125 else: 

1126 year, month, day = d.year, d.month, d.day 

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

1128 

1129 @staticmethod 

1130 def _parse_date_fmt(): 

1131 fmt = get_format("DATE_FORMAT") 

1132 escaped = False 

1133 for char in fmt: 

1134 if escaped: 

1135 escaped = False 

1136 elif char == "\\": 

1137 escaped = True 

1138 elif char in "Yy": 

1139 yield "year" 

1140 elif char in "bEFMmNn": 

1141 yield "month" 

1142 elif char in "dj": 

1143 yield "day" 

1144 

1145 def id_for_label(self, id_): 

1146 for first_select in self._parse_date_fmt(): 

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

1148 return "%s_month" % id_ 

1149 

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

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

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

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

1154 if y == m == d == "": 

1155 return None 

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

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

1158 input_format = formats.sanitize_strftime_format(input_format) 

1159 try: 

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

1161 except ValueError: 

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

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

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

1165 return date_value.strftime(input_format) 

1166 return data.get(name) 

1167 

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

1169 return not any( 

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

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

1172 )