Ticket #1714: readonly_fields_v2.patch
File readonly_fields_v2.patch, 24.1 KB (added by , 18 years ago) |
---|
-
django/forms/__init__.py
360 360 def get_id(self): 361 361 "Returns the HTML 'id' attribute for this form field." 362 362 return FORM_FIELD_ID_PREFIX + self.field_name 363 364 def css_class(self, all_classes=True): 365 """ 366 Returns the CSS classes for this form field. 367 368 If all_classes is False, only the main CSS class will be returned. 369 """ 370 required_class = '' 371 readonly_class = '' 372 if all_classes: 373 if self.required: 374 required_class = ' required' 375 if self.readonly: 376 readonly_class = ' readonly' 377 return 'v%s%s%s' % (self.__class__.__name__, required_class, readonly_class) 363 378 379 def html_readonly_attribute(self): 380 """ 381 Returns the correct HTML readonly attribute for this form field. 382 383 The readonly attribute only works for <input type="text">, 384 <input type="password"> and <textarea>. For everything else, the 385 disabled attribute should be used. 386 """ 387 if not self.readonly: 388 return '' 389 else: 390 input_type = getattr(self, 'input_type', '') 391 if self.input_type in ('text', 'password'): 392 return ' readonly="readonly"' 393 else: 394 return ' disabled="disabled"' 395 364 396 #################### 365 397 # GENERIC WIDGETS # 366 398 #################### 367 399 368 400 class TextField(FormField): 369 401 input_type = "text" 370 def __init__(self, field_name, length=30, maxlength=None, is_required=False, validator_list=None, member_name=None ):402 def __init__(self, field_name, length=30, maxlength=None, is_required=False, validator_list=None, member_name=None, readonly=False): 371 403 if validator_list is None: validator_list = [] 372 404 self.field_name = field_name 373 405 self.length, self.maxlength = length, maxlength … … 375 407 self.validator_list = [self.isValidLength, self.hasNoNewlines] + validator_list 376 408 if member_name != None: 377 409 self.member_name = member_name 410 self.readonly = readonly 378 411 379 412 def isValidLength(self, data, form): 380 413 if data and self.maxlength and len(data.decode(settings.DEFAULT_CHARSET)) > self.maxlength: … … 390 423 data = '' 391 424 maxlength = '' 392 425 if self.maxlength: 393 maxlength = ' maxlength="%s"' % self.maxlength426 maxlength = ' maxlength="%s"' % self.maxlength 394 427 if isinstance(data, unicode): 395 428 data = data.encode(settings.DEFAULT_CHARSET) 396 return '<input type="%s" id="%s" class=" v%s%s" name="%s" size="%s" value="%s" %s/>' % \397 (self.input_type, self.get_id(), self. __class__.__name__, self.is_required and ' required' or '',398 self.field_name, self.length, escape(data), maxlength)429 return '<input type="%s" id="%s" class="%s" name="%s" size="%s" value="%s"%s%s />' % \ 430 (self.input_type, self.get_id(), self.css_class(), self.field_name, self.length, 431 escape(data), maxlength, self.html_readonly_attribute()) 399 432 400 433 def html2python(data): 401 434 return data … … 405 438 input_type = "password" 406 439 407 440 class LargeTextField(TextField): 408 def __init__(self, field_name, rows=10, cols=40, is_required=False, validator_list=None, maxlength=None ):441 def __init__(self, field_name, rows=10, cols=40, is_required=False, validator_list=None, maxlength=None, readonly=False): 409 442 if validator_list is None: validator_list = [] 410 443 self.field_name = field_name 444 self.validator_list = validator_list[:] 411 445 self.rows, self.cols, self.is_required = rows, cols, is_required 412 self.validator_list = validator_list[:]413 446 if maxlength: 414 447 self.validator_list.append(self.isValidLength) 415 448 self.maxlength = maxlength 449 self.readonly = readonly 416 450 417 451 def render(self, data): 418 452 if data is None: 419 453 data = '' 420 454 if isinstance(data, unicode): 421 455 data = data.encode(settings.DEFAULT_CHARSET) 422 return '<textarea id="%s" class=" v%s%s" name="%s" rows="%s" cols="%s">%s</textarea>' % \423 (self.get_id(), self. __class__.__name__, self.is_required and ' required' or '',424 self.field_name, self.rows, self.cols, escape(data))456 return '<textarea id="%s" class="%s" name="%s" rows="%s" cols="%s"%s>%s</textarea>' % \ 457 (self.get_id(), self.css_class(), self.field_name, self.rows, self.cols, 458 self.html_readonly_attribute(), escape(data)) 425 459 426 460 class HiddenField(FormField): 427 461 def __init__(self, field_name, is_required=False, validator_list=None): … … 434 468 (self.get_id(), self.field_name, escape(data)) 435 469 436 470 class CheckboxField(FormField): 437 def __init__(self, field_name, checked_by_default=False, validator_list=None ):471 def __init__(self, field_name, checked_by_default=False, validator_list=None, readonly=False): 438 472 if validator_list is None: validator_list = [] 439 473 self.field_name = field_name 440 474 self.checked_by_default = checked_by_default 441 475 self.is_required = False # because the validator looks for these 442 476 self.validator_list = validator_list[:] 477 self.readonly = readonly 443 478 444 479 def render(self, data): 445 480 checked_html = '' 446 481 if data or (data is '' and self.checked_by_default): 447 482 checked_html = ' checked="checked"' 448 return '<input type="checkbox" id="%s" class=" v%s" name="%s"%s />' % \449 (self.get_id(), self. __class__.__name__,450 self.field_name, checked_html )483 return '<input type="checkbox" id="%s" class="%s" name="%s"%s%s />' % \ 484 (self.get_id(), self.css_class(all_classes=False), 485 self.field_name, checked_html, self.html_readonly_attribute()) 451 486 452 487 def html2python(data): 453 488 "Convert value from browser ('on' or '') to a Python boolean" … … 457 492 html2python = staticmethod(html2python) 458 493 459 494 class SelectField(FormField): 460 def __init__(self, field_name, choices=None, size=1, is_required=False, validator_list=None, member_name=None ):495 def __init__(self, field_name, choices=None, size=1, is_required=False, validator_list=None, member_name=None, readonly=False): 461 496 if validator_list is None: validator_list = [] 462 497 if choices is None: choices = [] 463 498 self.field_name = field_name … … 466 501 self.validator_list = [self.isValidChoice] + validator_list 467 502 if member_name != None: 468 503 self.member_name = member_name 504 self.readonly = readonly 469 505 470 506 def render(self, data): 471 output = ['<select id="%s" class=" v%s%s" name="%s" size="%s">' % \472 (self.get_id(), self. __class__.__name__,473 self. is_required and ' required' or '', self.field_name, self.size)]507 output = ['<select id="%s" class="%s" name="%s" size="%s"%s>' % \ 508 (self.get_id(), self.css_class(), self.field_name, self.size, 509 self.html_readonly_attribute())] 474 510 str_data = str(data) # normalize to string 475 511 for value, display_name in self.choices: 476 512 selected_html = '' … … 495 531 html2python = staticmethod(html2python) 496 532 497 533 class RadioSelectField(FormField): 498 def __init__(self, field_name, choices=None, ul_class='', is_required=False, validator_list=None, member_name=None ):534 def __init__(self, field_name, choices=None, ul_class='', is_required=False, validator_list=None, member_name=None, readonly=False): 499 535 if validator_list is None: validator_list = [] 500 536 if choices is None: choices = [] 501 537 self.field_name = field_name … … 505 541 self.ul_class = ul_class 506 542 if member_name != None: 507 543 self.member_name = member_name 544 self.readonly = readonly 508 545 509 546 def render(self, data): 510 547 """ … … 546 583 datalist.append({ 547 584 'value': value, 548 585 'name': display_name, 549 'field': '<input type="radio" id="%s" name="%s" value="%s"%s />' % \550 (self.get_id() + '_' + str(i), self.field_name, value, selected_html ),586 'field': '<input type="radio" id="%s" name="%s" value="%s"%s%s />' % \ 587 (self.get_id() + '_' + str(i), self.field_name, value, selected_html, self.html_readonly_attribute()), 551 588 'label': '<label for="%s">%s</label>' % \ 552 589 (self.get_id() + '_' + str(i), display_name), 553 590 }) … … 561 598 562 599 class NullBooleanField(SelectField): 563 600 "This SelectField provides 'Yes', 'No' and 'Unknown', mapping results to True, False or None" 564 def __init__(self, field_name, is_required=False, validator_list=None ):601 def __init__(self, field_name, is_required=False, validator_list=None, readonly=False): 565 602 if validator_list is None: validator_list = [] 566 603 SelectField.__init__(self, field_name, choices=[('1', 'Unknown'), ('2', 'Yes'), ('3', 'No')], 567 is_required=is_required, validator_list=validator_list )604 is_required=is_required, validator_list=validator_list, readonly=readonly) 568 605 569 606 def render(self, data): 570 607 if data is None: data = '1' … … 579 616 class SelectMultipleField(SelectField): 580 617 requires_data_list = True 581 618 def render(self, data): 582 output = ['<select id="%s" class=" v%s%s" name="%s" size="%s" multiple="multiple">' % \583 (self.get_id(), self. __class__.__name__, self.is_required and ' required' or '',584 self.field_name, self.size)]619 output = ['<select id="%s" class="%s" name="%s" size="%s" multiple="multiple"%s>' % \ 620 (self.get_id(), self.css_class(), self.field_name, self.size, 621 self.html_readonly_attribute())] 585 622 str_data_list = map(str, data) # normalize to strings 586 623 for value, choice in self.choices: 587 624 selected_html = '' … … 615 652 back into the single list that validators, renderers and save() expect. 616 653 """ 617 654 requires_data_list = True 618 def __init__(self, field_name, choices=None, ul_class='', validator_list=None ):655 def __init__(self, field_name, choices=None, ul_class='', validator_list=None, readonly=False): 619 656 if validator_list is None: validator_list = [] 620 657 if choices is None: choices = [] 621 658 self.ul_class = ul_class 622 SelectMultipleField.__init__(self, field_name, choices, size=1, is_required=False, validator_list=validator_list )659 SelectMultipleField.__init__(self, field_name, choices, size=1, is_required=False, validator_list=validator_list, readonly=readonly) 623 660 624 661 def prepare(self, new_data): 625 662 # new_data has "split" this field into several fields, so flatten it … … 638 675 if str(value) in str_data_list: 639 676 checked_html = ' checked="checked"' 640 677 field_name = '%s%s' % (self.field_name, value) 641 output.append('<li><input type="checkbox" id="%s" class=" v%s" name="%s"%s /> <label for="%s">%s</label></li>' % \642 (self.get_id() + value , self.__class__.__name__, field_name, checked_html,678 output.append('<li><input type="checkbox" id="%s" class="%s" name="%s"%s%s /> <label for="%s">%s</label></li>' % \ 679 (self.get_id() + value, self.css_class(all_classes=False), field_name, checked_html, self.html_readonly_attribute(), 643 680 self.get_id() + value, choice)) 644 681 output.append('</ul>') 645 682 return '\n'.join(output) … … 649 686 #################### 650 687 651 688 class FileUploadField(FormField): 652 def __init__(self, field_name, is_required=False, validator_list=None ):689 def __init__(self, field_name, is_required=False, validator_list=None, readonly=False): 653 690 if validator_list is None: validator_list = [] 654 691 self.field_name, self.is_required = field_name, is_required 655 692 self.validator_list = [self.isNonEmptyFile] + validator_list 693 self.readonly = readonly 656 694 657 695 def isNonEmptyFile(self, field_data, all_data): 658 696 try: … … 663 701 raise validators.CriticalValidationError, gettext("The submitted file is empty.") 664 702 665 703 def render(self, data): 666 return '<input type="file" id="%s" class=" v%s" name="%s"/>' % \667 (self.get_id(), self. __class__.__name__, self.field_name)704 return '<input type="file" id="%s" class="%s" name="%s"%s />' % \ 705 (self.get_id(), self.css_class(all_classes=False), self.field_name, self.html_readonly_attribute()) 668 706 669 707 def html2python(data): 670 708 if data is None: … … 689 727 #################### 690 728 691 729 class IntegerField(TextField): 692 def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=None, member_name=None ):730 def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=None, member_name=None, readonly=False): 693 731 if validator_list is None: validator_list = [] 694 732 validator_list = [self.isInteger] + validator_list 695 733 if member_name is not None: 696 734 self.member_name = member_name 697 TextField.__init__(self, field_name, length, maxlength, is_required, validator_list )735 TextField.__init__(self, field_name, length, maxlength, is_required, validator_list, readonly=readonly) 698 736 699 737 def isInteger(self, field_data, all_data): 700 738 try: … … 709 747 html2python = staticmethod(html2python) 710 748 711 749 class SmallIntegerField(IntegerField): 712 def __init__(self, field_name, length=5, maxlength=5, is_required=False, validator_list=None ):750 def __init__(self, field_name, length=5, maxlength=5, is_required=False, validator_list=None, readonly=False): 713 751 if validator_list is None: validator_list = [] 714 752 validator_list = [self.isSmallInteger] + validator_list 715 IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list )753 IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list, readonly) 716 754 717 755 def isSmallInteger(self, field_data, all_data): 718 756 if not -32768 <= int(field_data) <= 32767: 719 757 raise validators.CriticalValidationError, gettext("Enter a whole number between -32,768 and 32,767.") 720 758 721 759 class PositiveIntegerField(IntegerField): 722 def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=None ):760 def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=None, readonly=False): 723 761 if validator_list is None: validator_list = [] 724 762 validator_list = [self.isPositive] + validator_list 725 IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list )763 IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list, readonly) 726 764 727 765 def isPositive(self, field_data, all_data): 728 766 if int(field_data) < 0: 729 767 raise validators.CriticalValidationError, gettext("Enter a positive number.") 730 768 731 769 class PositiveSmallIntegerField(IntegerField): 732 def __init__(self, field_name, length=5, maxlength=None, is_required=False, validator_list=None ):770 def __init__(self, field_name, length=5, maxlength=None, is_required=False, validator_list=None, readonly=False): 733 771 if validator_list is None: validator_list = [] 734 772 validator_list = [self.isPositiveSmall] + validator_list 735 IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list )773 IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list, readonly) 736 774 737 775 def isPositiveSmall(self, field_data, all_data): 738 776 if not 0 <= int(field_data) <= 32767: 739 777 raise validators.CriticalValidationError, gettext("Enter a whole number between 0 and 32,767.") 740 778 741 779 class FloatField(TextField): 742 def __init__(self, field_name, max_digits, decimal_places, is_required=False, validator_list=None ):780 def __init__(self, field_name, max_digits, decimal_places, is_required=False, validator_list=None, readonly=False): 743 781 if validator_list is None: validator_list = [] 744 782 self.max_digits, self.decimal_places = max_digits, decimal_places 745 783 validator_list = [self.isValidFloat] + validator_list 746 TextField.__init__(self, field_name, max_digits+1, max_digits+1, is_required, validator_list )784 TextField.__init__(self, field_name, max_digits+1, max_digits+1, is_required, validator_list, readonly) 747 785 748 786 def isValidFloat(self, field_data, all_data): 749 787 v = validators.IsValidFloat(self.max_digits, self.decimal_places) … … 765 803 class DatetimeField(TextField): 766 804 """A FormField that automatically converts its data to a datetime.datetime object. 767 805 The data should be in the format YYYY-MM-DD HH:MM:SS.""" 768 def __init__(self, field_name, length=30, maxlength=None, is_required=False, validator_list=None ):806 def __init__(self, field_name, length=30, maxlength=None, is_required=False, validator_list=None, readonly=False): 769 807 if validator_list is None: validator_list = [] 770 808 self.field_name = field_name 771 809 self.length, self.maxlength = length, maxlength 772 810 self.is_required = is_required 773 811 self.validator_list = [validators.isValidANSIDatetime] + validator_list 812 self.readonly = readonly 774 813 775 814 def html2python(data): 776 815 "Converts the field into a datetime.datetime object" … … 792 831 class DateField(TextField): 793 832 """A FormField that automatically converts its data to a datetime.date object. 794 833 The data should be in the format YYYY-MM-DD.""" 795 def __init__(self, field_name, is_required=False, validator_list=None ):834 def __init__(self, field_name, is_required=False, validator_list=None, readonly=False): 796 835 if validator_list is None: validator_list = [] 797 836 validator_list = [self.isValidDate] + validator_list 798 837 TextField.__init__(self, field_name, length=10, maxlength=10, 799 is_required=is_required, validator_list=validator_list )838 is_required=is_required, validator_list=validator_list, readonly=readonly) 800 839 801 840 def isValidDate(self, field_data, all_data): 802 841 try: … … 817 856 class TimeField(TextField): 818 857 """A FormField that automatically converts its data to a datetime.time object. 819 858 The data should be in the format HH:MM:SS or HH:MM:SS.mmmmmm.""" 820 def __init__(self, field_name, is_required=False, validator_list=None ):859 def __init__(self, field_name, is_required=False, validator_list=None, readonly=False): 821 860 if validator_list is None: validator_list = [] 822 861 validator_list = [self.isValidTime] + validator_list 823 862 TextField.__init__(self, field_name, length=8, maxlength=8, 824 is_required=is_required, validator_list=validator_list )863 is_required=is_required, validator_list=validator_list, readonly=readonly) 825 864 826 865 def isValidTime(self, field_data, all_data): 827 866 try: … … 852 891 853 892 class EmailField(TextField): 854 893 "A convenience FormField for validating e-mail addresses" 855 def __init__(self, field_name, length=50, maxlength=75, is_required=False, validator_list=None ):894 def __init__(self, field_name, length=50, maxlength=75, is_required=False, validator_list=None, readonly=False): 856 895 if validator_list is None: validator_list = [] 857 896 validator_list = [self.isValidEmail] + validator_list 858 897 TextField.__init__(self, field_name, length, maxlength=maxlength, 859 is_required=is_required, validator_list=validator_list )898 is_required=is_required, validator_list=validator_list, readonly=readonly) 860 899 861 900 def isValidEmail(self, field_data, all_data): 862 901 try: … … 866 905 867 906 class URLField(TextField): 868 907 "A convenience FormField for validating URLs" 869 def __init__(self, field_name, length=50, maxlength=200, is_required=False, validator_list=None ):908 def __init__(self, field_name, length=50, maxlength=200, is_required=False, validator_list=None, readonly=False): 870 909 if validator_list is None: validator_list = [] 871 910 validator_list = [self.isValidURL] + validator_list 872 911 TextField.__init__(self, field_name, length=length, maxlength=maxlength, 873 is_required=is_required, validator_list=validator_list )912 is_required=is_required, validator_list=validator_list, readonly=readonly) 874 913 875 914 def isValidURL(self, field_data, all_data): 876 915 try: … … 879 918 raise validators.CriticalValidationError, e.messages 880 919 881 920 class IPAddressField(TextField): 882 def __init__(self, field_name, length=15, maxlength=15, is_required=False, validator_list=None ):921 def __init__(self, field_name, length=15, maxlength=15, is_required=False, validator_list=None, readonly=False): 883 922 if validator_list is None: validator_list = [] 884 923 validator_list = [self.isValidIPAddress] + validator_list 885 924 TextField.__init__(self, field_name, length=length, maxlength=maxlength, 886 is_required=is_required, validator_list=validator_list )925 is_required=is_required, validator_list=validator_list, readonly=readonly) 887 926 888 927 def isValidIPAddress(self, field_data, all_data): 889 928 try: … … 901 940 902 941 class FilePathField(SelectField): 903 942 "A SelectField whose choices are the files in a given directory." 904 def __init__(self, field_name, path, match=None, recursive=False, is_required=False, validator_list=None ):943 def __init__(self, field_name, path, match=None, recursive=False, is_required=False, validator_list=None, readonly=False): 905 944 import os 906 945 from django.db.models import BLANK_CHOICE_DASH 907 946 if match is not None: … … 921 960 choices.append((full_file, f)) 922 961 except OSError: 923 962 pass 924 SelectField.__init__(self, field_name, choices, 1, is_required, validator_list )963 SelectField.__init__(self, field_name, choices, 1, is_required, validator_list, readonly) 925 964 926 965 class PhoneNumberField(TextField): 927 966 "A convenience FormField for validating phone numbers (e.g. '630-555-1234')" 928 def __init__(self, field_name, is_required=False, validator_list=None ):967 def __init__(self, field_name, is_required=False, validator_list=None, readonly=False): 929 968 if validator_list is None: validator_list = [] 930 969 validator_list = [self.isValidPhone] + validator_list 931 970 TextField.__init__(self, field_name, length=12, maxlength=12, 932 is_required=is_required, validator_list=validator_list )971 is_required=is_required, validator_list=validator_list, readonly=readonly) 933 972 934 973 def isValidPhone(self, field_data, all_data): 935 974 try: … … 939 978 940 979 class USStateField(TextField): 941 980 "A convenience FormField for validating U.S. states (e.g. 'IL')" 942 def __init__(self, field_name, is_required=False, validator_list=None ):981 def __init__(self, field_name, is_required=False, validator_list=None, readonly=False): 943 982 if validator_list is None: validator_list = [] 944 983 validator_list = [self.isValidUSState] + validator_list 945 984 TextField.__init__(self, field_name, length=2, maxlength=2, 946 is_required=is_required, validator_list=validator_list )985 is_required=is_required, validator_list=validator_list, readonly=readonly) 947 986 948 987 def isValidUSState(self, field_data, all_data): 949 988 try: … … 960 999 961 1000 class CommaSeparatedIntegerField(TextField): 962 1001 "A convenience FormField for validating comma-separated integer fields" 963 def __init__(self, field_name, maxlength=None, is_required=False, validator_list=None ):1002 def __init__(self, field_name, maxlength=None, is_required=False, validator_list=None, readonly=False): 964 1003 if validator_list is None: validator_list = [] 965 1004 validator_list = [self.isCommaSeparatedIntegerList] + validator_list 966 1005 TextField.__init__(self, field_name, length=20, maxlength=maxlength, 967 is_required=is_required, validator_list=validator_list )1006 is_required=is_required, validator_list=validator_list, readonly=readonly) 968 1007 969 1008 def isCommaSeparatedIntegerList(self, field_data, all_data): 970 1009 try: