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

632 statements  

« prev     ^ index     » next       coverage.py v7.0.5, created at 2023-01-17 06:13 +0000

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: 

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

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

67 else: 

68 if css is None: 

69 css = {} 

70 if js is None: 

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 path.__html__() 

105 if hasattr(path, "__html__") 

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

107 for path in self._js 

108 ] 

109 

110 def render_css(self): 

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

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

113 media = sorted(self._css) 

114 return chain.from_iterable( 

115 [ 

116 path.__html__() 

117 if hasattr(path, "__html__") 

118 else format_html( 

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

120 self.absolute_path(path), 

121 medium, 

122 ) 

123 for path in self._css[medium] 

124 ] 

125 for medium in media 

126 ) 

127 

128 def absolute_path(self, path): 

129 """ 

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

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

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

133 """ 

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

135 return path 

136 return static(path) 

137 

138 def __getitem__(self, name): 

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

140 if name in MEDIA_TYPES: 

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

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

143 

144 @staticmethod 

145 def merge(*lists): 

146 """ 

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

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

149 

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

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

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

153 """ 

154 dependency_graph = defaultdict(set) 

155 all_items = OrderedSet() 

156 for list_ in filter(None, lists): 

157 head = list_[0] 

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

159 # dependency graph to be included in the result. 

160 dependency_graph.setdefault(head, set()) 

161 for item in list_: 

162 all_items.add(item) 

163 # No self dependencies 

164 if head != item: 

165 dependency_graph[item].add(head) 

166 head = item 

167 try: 

168 return stable_topological_sort(all_items, dependency_graph) 

169 except CyclicDependencyError: 

170 warnings.warn( 

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

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

173 ), 

174 MediaOrderConflictWarning, 

175 ) 

176 return list(all_items) 

177 

178 def __add__(self, other): 

179 combined = Media() 

180 combined._css_lists = self._css_lists[:] 

181 combined._js_lists = self._js_lists[:] 

182 for item in other._css_lists: 

183 if item and item not in self._css_lists: 

184 combined._css_lists.append(item) 

185 for item in other._js_lists: 

186 if item and item not in self._js_lists: 

187 combined._js_lists.append(item) 

188 return combined 

189 

190 

191def media_property(cls): 

192 def _media(self): 

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

194 sup_cls = super(cls, self) 

195 try: 

196 base = sup_cls.media 

197 except AttributeError: 

198 base = Media() 

199 

200 # Get the media definition for this class 

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

202 if definition: 

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

204 if extend: 

205 if extend is True: 

206 m = base 

207 else: 

208 m = Media() 

209 for medium in extend: 

210 m += base[medium] 

211 return m + Media(definition) 

212 return Media(definition) 

213 return base 

214 

215 return property(_media) 

216 

217 

218class MediaDefiningClass(type): 

219 """ 

220 Metaclass for classes that can have media definitions. 

221 """ 

222 

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

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

225 

226 if "media" not in attrs: 

227 new_class.media = media_property(new_class) 

228 

229 return new_class 

230 

231 

232class Widget(metaclass=MediaDefiningClass): 

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

234 is_localized = False 

235 is_required = False 

236 supports_microseconds = True 

237 use_fieldset = False 

238 

239 def __init__(self, attrs=None): 

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

241 

242 def __deepcopy__(self, memo): 

243 obj = copy.copy(self) 

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

245 memo[id(self)] = obj 

246 return obj 

247 

248 @property 

249 def is_hidden(self): 

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

251 

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

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

254 yield context["widget"] 

255 

256 def format_value(self, value): 

257 """ 

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

259 """ 

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

261 return None 

262 if self.is_localized: 

263 return formats.localize_input(value) 

264 return str(value) 

265 

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

267 return { 

268 "widget": { 

269 "name": name, 

270 "is_hidden": self.is_hidden, 

271 "required": self.is_required, 

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

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

274 "template_name": self.template_name, 

275 }, 

276 } 

277 

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

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

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

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

282 

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

284 if renderer is None: 

285 renderer = get_default_renderer() 

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

287 

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

289 """Build an attribute dictionary.""" 

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

291 

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

293 """ 

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

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

296 """ 

297 return data.get(name) 

298 

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

300 return name not in data 

301 

302 def id_for_label(self, id_): 

303 """ 

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

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

306 

307 This hook is necessary because some widgets have multiple HTML 

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

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

310 tags. 

311 """ 

312 return id_ 

313 

314 def use_required_attribute(self, initial): 

315 return not self.is_hidden 

316 

317 

318class Input(Widget): 

319 """ 

320 Base class for all <input> widgets. 

321 """ 

322 

323 input_type = None # Subclasses must define this. 

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

325 

326 def __init__(self, attrs=None): 

327 if attrs is not None: 

328 attrs = attrs.copy() 

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

330 super().__init__(attrs) 

331 

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

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

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

335 return context 

336 

337 

338class TextInput(Input): 

339 input_type = "text" 

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

341 

342 

343class NumberInput(Input): 

344 input_type = "number" 

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

346 

347 

348class EmailInput(Input): 

349 input_type = "email" 

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

351 

352 

353class URLInput(Input): 

354 input_type = "url" 

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

356 

357 

358class PasswordInput(Input): 

359 input_type = "password" 

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

361 

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

363 super().__init__(attrs) 

364 self.render_value = render_value 

365 

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

367 if not self.render_value: 

368 value = None 

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

370 

371 

372class HiddenInput(Input): 

373 input_type = "hidden" 

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

375 

376 

377class MultipleHiddenInput(HiddenInput): 

378 """ 

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

380 of values. 

381 """ 

382 

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

384 

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

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

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

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

389 

390 subwidgets = [] 

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

392 widget_attrs = final_attrs.copy() 

393 if id_: 

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

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

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

397 widget = HiddenInput() 

398 widget.is_required = self.is_required 

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

400 

401 context["widget"]["subwidgets"] = subwidgets 

402 return context 

403 

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

405 try: 

406 getter = data.getlist 

407 except AttributeError: 

408 getter = data.get 

409 return getter(name) 

410 

411 def format_value(self, value): 

412 return [] if value is None else value 

413 

414 

415class FileInput(Input): 

416 input_type = "file" 

417 needs_multipart_form = True 

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

419 

420 def format_value(self, value): 

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

422 return 

423 

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

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

426 return files.get(name) 

427 

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

429 return name not in files 

430 

431 def use_required_attribute(self, initial): 

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

433 

434 

435FILE_INPUT_CONTRADICTION = object() 

436 

437 

438class ClearableFileInput(FileInput): 

439 clear_checkbox_label = _("Clear") 

440 initial_text = _("Currently") 

441 input_text = _("Change") 

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

443 

444 def clear_checkbox_name(self, name): 

445 """ 

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

447 input. 

448 """ 

449 return name + "-clear" 

450 

451 def clear_checkbox_id(self, name): 

452 """ 

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

454 """ 

455 return name + "_id" 

456 

457 def is_initial(self, value): 

458 """ 

459 Return whether value is considered to be initial value. 

460 """ 

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

462 

463 def format_value(self, value): 

464 """ 

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

466 """ 

467 if self.is_initial(value): 

468 return value 

469 

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

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

472 checkbox_name = self.clear_checkbox_name(name) 

473 checkbox_id = self.clear_checkbox_id(checkbox_name) 

474 context["widget"].update( 

475 { 

476 "checkbox_name": checkbox_name, 

477 "checkbox_id": checkbox_id, 

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

479 "input_text": self.input_text, 

480 "initial_text": self.initial_text, 

481 "clear_checkbox_label": self.clear_checkbox_label, 

482 } 

483 ) 

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

485 return context 

486 

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

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

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

490 data, files, self.clear_checkbox_name(name) 

491 ): 

492 

493 if upload: 

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

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

496 # object that FileField will turn into a ValidationError. 

497 return FILE_INPUT_CONTRADICTION 

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

499 return False 

500 return upload 

501 

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

503 return ( 

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

505 and self.clear_checkbox_name(name) not in data 

506 ) 

507 

508 

509class Textarea(Widget): 

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

511 

512 def __init__(self, attrs=None): 

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

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

515 if attrs: 

516 default_attrs.update(attrs) 

517 super().__init__(default_attrs) 

518 

519 

520class DateTimeBaseInput(TextInput): 

521 format_key = "" 

522 supports_microseconds = False 

523 

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

525 super().__init__(attrs) 

526 self.format = format or None 

527 

528 def format_value(self, value): 

529 return formats.localize_input( 

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

531 ) 

532 

533 

534class DateInput(DateTimeBaseInput): 

535 format_key = "DATE_INPUT_FORMATS" 

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

537 

538 

539class DateTimeInput(DateTimeBaseInput): 

540 format_key = "DATETIME_INPUT_FORMATS" 

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

542 

543 

544class TimeInput(DateTimeBaseInput): 

545 format_key = "TIME_INPUT_FORMATS" 

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

547 

548 

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

550def boolean_check(v): 

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

552 

553 

554class CheckboxInput(Input): 

555 input_type = "checkbox" 

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

557 

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

559 super().__init__(attrs) 

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

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

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

563 

564 def format_value(self, value): 

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

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

567 return 

568 return str(value) 

569 

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

571 if self.check_test(value): 

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

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

574 

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

576 if name not in data: 

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

578 # send results for unselected checkboxes. 

579 return False 

580 value = data.get(name) 

581 # Translate true and false strings to boolean values. 

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

583 if isinstance(value, str): 

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

585 return bool(value) 

586 

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

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

589 # never known if the value is actually omitted. 

590 return False 

591 

592 

593class ChoiceWidget(Widget): 

594 allow_multiple_selected = False 

595 input_type = None 

596 template_name = None 

597 option_template_name = None 

598 add_id_index = True 

599 checked_attribute = {"checked": True} 

600 option_inherits_attrs = True 

601 

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

603 super().__init__(attrs) 

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

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

606 # more than once. 

607 self.choices = list(choices) 

608 

609 def __deepcopy__(self, memo): 

610 obj = copy.copy(self) 

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

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

613 memo[id(self)] = obj 

614 return obj 

615 

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

617 """ 

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

619 options from a BoundField for choice widgets. 

620 """ 

621 value = self.format_value(value) 

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

623 

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

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

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

627 yield from group[1] 

628 

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

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

631 groups = [] 

632 has_selected = False 

633 

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

635 if option_value is None: 

636 option_value = "" 

637 

638 subgroup = [] 

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

640 group_name = option_value 

641 subindex = 0 

642 choices = option_label 

643 else: 

644 group_name = None 

645 subindex = None 

646 choices = [(option_value, option_label)] 

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

648 

649 for subvalue, sublabel in choices: 

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

651 subvalue 

652 ) in value 

653 has_selected |= selected 

654 subgroup.append( 

655 self.create_option( 

656 name, 

657 subvalue, 

658 sublabel, 

659 selected, 

660 index, 

661 subindex=subindex, 

662 attrs=attrs, 

663 ) 

664 ) 

665 if subindex is not None: 

666 subindex += 1 

667 return groups 

668 

669 def create_option( 

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

671 ): 

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

673 option_attrs = ( 

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

675 ) 

676 if selected: 

677 option_attrs.update(self.checked_attribute) 

678 if "id" in option_attrs: 

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

680 return { 

681 "name": name, 

682 "value": value, 

683 "label": label, 

684 "selected": selected, 

685 "index": index, 

686 "attrs": option_attrs, 

687 "type": self.input_type, 

688 "template_name": self.option_template_name, 

689 "wrap_label": True, 

690 } 

691 

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

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

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

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

696 ) 

697 return context 

698 

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

700 """ 

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

702 references the zero index. 

703 """ 

704 if id_ and self.add_id_index: 

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

706 return id_ 

707 

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

709 getter = data.get 

710 if self.allow_multiple_selected: 

711 try: 

712 getter = data.getlist 

713 except AttributeError: 

714 pass 

715 return getter(name) 

716 

717 def format_value(self, value): 

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

719 if value is None and self.allow_multiple_selected: 

720 return [] 

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

722 value = [value] 

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

724 

725 

726class Select(ChoiceWidget): 

727 input_type = "select" 

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

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

730 add_id_index = False 

731 checked_attribute = {"selected": True} 

732 option_inherits_attrs = False 

733 

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

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

736 if self.allow_multiple_selected: 

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

738 return context 

739 

740 @staticmethod 

741 def _choice_has_empty_value(choice): 

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

743 value, _ = choice 

744 return value is None or value == "" 

745 

746 def use_required_attribute(self, initial): 

747 """ 

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

749 invalid HTML. 

750 """ 

751 use_required_attribute = super().use_required_attribute(initial) 

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

753 if self.allow_multiple_selected: 

754 return use_required_attribute 

755 

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

757 return ( 

758 use_required_attribute 

759 and first_choice is not None 

760 and self._choice_has_empty_value(first_choice) 

761 ) 

762 

763 

764class NullBooleanSelect(Select): 

765 """ 

766 A Select Widget intended to be used with NullBooleanField. 

767 """ 

768 

769 def __init__(self, attrs=None): 

770 choices = ( 

771 ("unknown", _("Unknown")), 

772 ("true", _("Yes")), 

773 ("false", _("No")), 

774 ) 

775 super().__init__(attrs, choices) 

776 

777 def format_value(self, value): 

778 try: 

779 return { 

780 True: "true", 

781 False: "false", 

782 "true": "true", 

783 "false": "false", 

784 # For backwards compatibility with Django < 2.2. 

785 "2": "true", 

786 "3": "false", 

787 }[value] 

788 except KeyError: 

789 return "unknown" 

790 

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

792 value = data.get(name) 

793 return { 

794 True: True, 

795 "True": True, 

796 "False": False, 

797 False: False, 

798 "true": True, 

799 "false": False, 

800 # For backwards compatibility with Django < 2.2. 

801 "2": True, 

802 "3": False, 

803 }.get(value) 

804 

805 

806class SelectMultiple(Select): 

807 allow_multiple_selected = True 

808 

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

810 try: 

811 getter = data.getlist 

812 except AttributeError: 

813 getter = data.get 

814 return getter(name) 

815 

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

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

818 # never known if the value is actually omitted. 

819 return False 

820 

821 

822class RadioSelect(ChoiceWidget): 

823 input_type = "radio" 

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

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

826 use_fieldset = True 

827 

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

829 """ 

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

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

832 the first input. 

833 """ 

834 if index is None: 

835 return "" 

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

837 

838 

839class CheckboxSelectMultiple(RadioSelect): 

840 allow_multiple_selected = True 

841 input_type = "checkbox" 

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

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

844 

845 def use_required_attribute(self, initial): 

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

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

848 return False 

849 

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

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

852 # never known if the value is actually omitted. 

853 return False 

854 

855 

856class MultiWidget(Widget): 

857 """ 

858 A widget that is composed of multiple widgets. 

859 

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

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

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

863 

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

865 """ 

866 

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

868 use_fieldset = True 

869 

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

871 if isinstance(widgets, dict): 

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

873 widgets = widgets.values() 

874 else: 

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

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

877 super().__init__(attrs) 

878 

879 @property 

880 def is_hidden(self): 

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

882 

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

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

885 if self.is_localized: 

886 for widget in self.widgets: 

887 widget.is_localized = self.is_localized 

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

889 # in self.widgets. 

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

891 value = self.decompress(value) 

892 

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

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

895 id_ = final_attrs.get("id") 

896 subwidgets = [] 

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

898 zip(self.widgets_names, self.widgets) 

899 ): 

900 if input_type is not None: 

901 widget.input_type = input_type 

902 widget_name = name + widget_name 

903 try: 

904 widget_value = value[i] 

905 except IndexError: 

906 widget_value = None 

907 if id_: 

908 widget_attrs = final_attrs.copy() 

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

910 else: 

911 widget_attrs = final_attrs 

912 subwidgets.append( 

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

914 ) 

915 context["widget"]["subwidgets"] = subwidgets 

916 return context 

917 

918 def id_for_label(self, id_): 

919 return "" 

920 

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

922 return [ 

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

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

925 ] 

926 

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

928 return all( 

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

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

931 ) 

932 

933 def decompress(self, value): 

934 """ 

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

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

937 non-empty. 

938 """ 

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

940 

941 def _get_media(self): 

942 """ 

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

944 subwidgets. 

945 """ 

946 media = Media() 

947 for w in self.widgets: 

948 media += w.media 

949 return media 

950 

951 media = property(_get_media) 

952 

953 def __deepcopy__(self, memo): 

954 obj = super().__deepcopy__(memo) 

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

956 return obj 

957 

958 @property 

959 def needs_multipart_form(self): 

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

961 

962 

963class SplitDateTimeWidget(MultiWidget): 

964 """ 

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

966 """ 

967 

968 supports_microseconds = False 

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

970 

971 def __init__( 

972 self, 

973 attrs=None, 

974 date_format=None, 

975 time_format=None, 

976 date_attrs=None, 

977 time_attrs=None, 

978 ): 

979 widgets = ( 

980 DateInput( 

981 attrs=attrs if date_attrs is None else date_attrs, 

982 format=date_format, 

983 ), 

984 TimeInput( 

985 attrs=attrs if time_attrs is None else time_attrs, 

986 format=time_format, 

987 ), 

988 ) 

989 super().__init__(widgets) 

990 

991 def decompress(self, value): 

992 if value: 

993 value = to_current_timezone(value) 

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

995 return [None, None] 

996 

997 

998class SplitHiddenDateTimeWidget(SplitDateTimeWidget): 

999 """ 

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

1001 """ 

1002 

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

1004 

1005 def __init__( 

1006 self, 

1007 attrs=None, 

1008 date_format=None, 

1009 time_format=None, 

1010 date_attrs=None, 

1011 time_attrs=None, 

1012 ): 

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

1014 for widget in self.widgets: 

1015 widget.input_type = "hidden" 

1016 

1017 

1018class SelectDateWidget(Widget): 

1019 """ 

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

1021 

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

1023 element and hence implements value_from_datadict. 

1024 """ 

1025 

1026 none_value = ("", "---") 

1027 month_field = "%s_month" 

1028 day_field = "%s_day" 

1029 year_field = "%s_year" 

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

1031 input_type = "select" 

1032 select_widget = Select 

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

1034 use_fieldset = True 

1035 

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

1037 self.attrs = attrs or {} 

1038 

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

1040 if years: 

1041 self.years = years 

1042 else: 

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

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

1045 

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

1047 if months: 

1048 self.months = months 

1049 else: 

1050 self.months = MONTHS 

1051 

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

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

1054 if not len(empty_label) == 3: 

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

1056 

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

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

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

1060 else: 

1061 if empty_label is not None: 

1062 self.none_value = ("", empty_label) 

1063 

1064 self.year_none_value = self.none_value 

1065 self.month_none_value = self.none_value 

1066 self.day_none_value = self.none_value 

1067 

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

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

1070 date_context = {} 

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

1072 if not self.is_required: 

1073 year_choices.insert(0, self.year_none_value) 

1074 year_name = self.year_field % name 

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

1076 attrs, choices=year_choices 

1077 ).get_context( 

1078 name=year_name, 

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

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

1081 ) 

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

1083 if not self.is_required: 

1084 month_choices.insert(0, self.month_none_value) 

1085 month_name = self.month_field % name 

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

1087 attrs, choices=month_choices 

1088 ).get_context( 

1089 name=month_name, 

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

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

1092 ) 

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

1094 if not self.is_required: 

1095 day_choices.insert(0, self.day_none_value) 

1096 day_name = self.day_field % name 

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

1098 attrs, 

1099 choices=day_choices, 

1100 ).get_context( 

1101 name=day_name, 

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

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

1104 ) 

1105 subwidgets = [] 

1106 for field in self._parse_date_fmt(): 

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

1108 context["widget"]["subwidgets"] = subwidgets 

1109 return context 

1110 

1111 def format_value(self, value): 

1112 """ 

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

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

1115 31 to display correctly. 

1116 """ 

1117 year, month, day = None, None, None 

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

1119 year, month, day = value.year, value.month, value.day 

1120 elif isinstance(value, str): 

1121 match = self.date_re.match(value) 

1122 if match: 

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

1124 # empty option value. 

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

1126 else: 

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

1128 try: 

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

1130 except ValueError: 

1131 pass 

1132 else: 

1133 year, month, day = d.year, d.month, d.day 

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

1135 

1136 @staticmethod 

1137 def _parse_date_fmt(): 

1138 fmt = get_format("DATE_FORMAT") 

1139 escaped = False 

1140 for char in fmt: 

1141 if escaped: 

1142 escaped = False 

1143 elif char == "\\": 

1144 escaped = True 

1145 elif char in "Yy": 

1146 yield "year" 

1147 elif char in "bEFMmNn": 

1148 yield "month" 

1149 elif char in "dj": 

1150 yield "day" 

1151 

1152 def id_for_label(self, id_): 

1153 for first_select in self._parse_date_fmt(): 

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

1155 return "%s_month" % id_ 

1156 

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

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

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

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

1161 if y == m == d == "": 

1162 return None 

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

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

1165 input_format = formats.sanitize_strftime_format(input_format) 

1166 try: 

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

1168 except ValueError: 

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

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

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

1172 return date_value.strftime(input_format) 

1173 return data.get(name) 

1174 

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

1176 return not any( 

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

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

1179 )