Ticket #1714: readonly_fields_v2.2.patch
File readonly_fields_v2.2.patch, 25.4 KB (added by , 18 years ago) |
---|
-
django/db/models/fields/__init__.py
264 264 if core_field_names: 265 265 params['validator_list'].append(validators.RequiredIfOtherFieldsGiven(core_field_names, gettext_lazy("This field is required."))) 266 266 267 # If this field is not editable, then use the readonly attribute FormFields. 268 params['is_readonly'] = not self.editable 269 267 270 # Finally, add the field_names. 268 271 field_names = self.get_manipulator_field_names(name_prefix) 269 272 return [man(field_name=field_names[i], **params) for i, man in enumerate(field_objs)] -
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.is_required: 374 required_class = ' required' 375 if self.is_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.is_readonly: 388 return '' 389 else: 390 input_type = getattr(self, 'input_type', '') 391 if 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, is_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.is_readonly = is_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, is_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.is_readonly = is_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 def __init__(self, field_name, is_required=False, validator_list=None ):461 def __init__(self, field_name, is_required=False, validator_list=None, is_readonly=False): 428 462 if validator_list is None: validator_list = [] 429 463 self.field_name, self.is_required = field_name, is_required 430 464 self.validator_list = validator_list[:] 465 self.is_readonly = is_readonly 431 466 432 467 def render(self, data): 433 468 return '<input type="hidden" id="%s" name="%s" value="%s" />' % \ 434 469 (self.get_id(), self.field_name, escape(data)) 435 470 436 471 class CheckboxField(FormField): 437 def __init__(self, field_name, checked_by_default=False, validator_list=None ):472 def __init__(self, field_name, checked_by_default=False, validator_list=None, is_readonly=False): 438 473 if validator_list is None: validator_list = [] 439 474 self.field_name = field_name 440 475 self.checked_by_default = checked_by_default 441 476 self.is_required = False # because the validator looks for these 442 477 self.validator_list = validator_list[:] 478 self.is_readonly = is_readonly 443 479 444 480 def render(self, data): 445 481 checked_html = '' 446 482 if data or (data is '' and self.checked_by_default): 447 483 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 )484 return '<input type="checkbox" id="%s" class="%s" name="%s"%s%s />' % \ 485 (self.get_id(), self.css_class(all_classes=False), 486 self.field_name, checked_html, self.html_readonly_attribute()) 451 487 452 488 def html2python(data): 453 489 "Convert value from browser ('on' or '') to a Python boolean" … … 457 493 html2python = staticmethod(html2python) 458 494 459 495 class SelectField(FormField): 460 def __init__(self, field_name, choices=None, size=1, is_required=False, validator_list=None, member_name=None ):496 def __init__(self, field_name, choices=None, size=1, is_required=False, validator_list=None, member_name=None, is_readonly=False): 461 497 if validator_list is None: validator_list = [] 462 498 if choices is None: choices = [] 463 499 self.field_name = field_name … … 466 502 self.validator_list = [self.isValidChoice] + validator_list 467 503 if member_name != None: 468 504 self.member_name = member_name 505 self.is_readonly = is_readonly 469 506 470 507 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)]508 output = ['<select id="%s" class="%s" name="%s" size="%s"%s>' % \ 509 (self.get_id(), self.css_class(), self.field_name, self.size, 510 self.html_readonly_attribute())] 474 511 str_data = str(data) # normalize to string 475 512 for value, display_name in self.choices: 476 513 selected_html = '' … … 495 532 html2python = staticmethod(html2python) 496 533 497 534 class RadioSelectField(FormField): 498 def __init__(self, field_name, choices=None, ul_class='', is_required=False, validator_list=None, member_name=None ):535 def __init__(self, field_name, choices=None, ul_class='', is_required=False, validator_list=None, member_name=None, is_readonly=False): 499 536 if validator_list is None: validator_list = [] 500 537 if choices is None: choices = [] 501 538 self.field_name = field_name … … 505 542 self.ul_class = ul_class 506 543 if member_name != None: 507 544 self.member_name = member_name 545 self.is_readonly = is_readonly 508 546 509 547 def render(self, data): 510 548 """ … … 546 584 datalist.append({ 547 585 'value': value, 548 586 '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 ),587 'field': '<input type="radio" id="%s" name="%s" value="%s"%s%s />' % \ 588 (self.get_id() + '_' + str(i), self.field_name, value, selected_html, self.html_readonly_attribute()), 551 589 'label': '<label for="%s">%s</label>' % \ 552 590 (self.get_id() + '_' + str(i), display_name), 553 591 }) … … 561 599 562 600 class NullBooleanField(SelectField): 563 601 "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 ):602 def __init__(self, field_name, is_required=False, validator_list=None, is_readonly=False): 565 603 if validator_list is None: validator_list = [] 566 604 SelectField.__init__(self, field_name, choices=[('1', 'Unknown'), ('2', 'Yes'), ('3', 'No')], 567 is_required=is_required, validator_list=validator_list )605 is_required=is_required, validator_list=validator_list, is_readonly=is_readonly) 568 606 569 607 def render(self, data): 570 608 if data is None: data = '1' … … 579 617 class SelectMultipleField(SelectField): 580 618 requires_data_list = True 581 619 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)]620 output = ['<select id="%s" class="%s" name="%s" size="%s" multiple="multiple"%s>' % \ 621 (self.get_id(), self.css_class(), self.field_name, self.size, 622 self.html_readonly_attribute())] 585 623 str_data_list = map(str, data) # normalize to strings 586 624 for value, choice in self.choices: 587 625 selected_html = '' … … 615 653 back into the single list that validators, renderers and save() expect. 616 654 """ 617 655 requires_data_list = True 618 def __init__(self, field_name, choices=None, ul_class='', validator_list=None ):656 def __init__(self, field_name, choices=None, ul_class='', validator_list=None, is_readonly=False): 619 657 if validator_list is None: validator_list = [] 620 658 if choices is None: choices = [] 621 659 self.ul_class = ul_class 622 SelectMultipleField.__init__(self, field_name, choices, size=1, is_required=False, validator_list=validator_list )660 SelectMultipleField.__init__(self, field_name, choices, size=1, is_required=False, validator_list=validator_list, is_readonly=is_readonly) 623 661 624 662 def prepare(self, new_data): 625 663 # new_data has "split" this field into several fields, so flatten it … … 638 676 if str(value) in str_data_list: 639 677 checked_html = ' checked="checked"' 640 678 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,679 output.append('<li><input type="checkbox" id="%s" class="%s" name="%s"%s%s /> <label for="%s">%s</label></li>' % \ 680 (self.get_id() + value, self.css_class(all_classes=False), field_name, checked_html, self.html_readonly_attribute(), 643 681 self.get_id() + value, choice)) 644 682 output.append('</ul>') 645 683 return '\n'.join(output) … … 649 687 #################### 650 688 651 689 class FileUploadField(FormField): 652 def __init__(self, field_name, is_required=False, validator_list=None ):690 def __init__(self, field_name, is_required=False, validator_list=None, is_readonly=False): 653 691 if validator_list is None: validator_list = [] 654 692 self.field_name, self.is_required = field_name, is_required 655 693 self.validator_list = [self.isNonEmptyFile] + validator_list 694 self.is_readonly = is_readonly 656 695 657 696 def isNonEmptyFile(self, field_data, all_data): 658 697 try: … … 663 702 raise validators.CriticalValidationError, gettext("The submitted file is empty.") 664 703 665 704 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)705 return '<input type="file" id="%s" class="%s" name="%s"%s />' % \ 706 (self.get_id(), self.css_class(all_classes=False), self.field_name, self.html_readonly_attribute()) 668 707 669 708 def html2python(data): 670 709 if data is None: … … 689 728 #################### 690 729 691 730 class IntegerField(TextField): 692 def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=None, member_name=None ):731 def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=None, member_name=None, is_readonly=False): 693 732 if validator_list is None: validator_list = [] 694 733 validator_list = [self.isInteger] + validator_list 695 734 if member_name is not None: 696 735 self.member_name = member_name 697 TextField.__init__(self, field_name, length, maxlength, is_required, validator_list )736 TextField.__init__(self, field_name, length, maxlength, is_required, validator_list, is_readonly=is_readonly) 698 737 699 738 def isInteger(self, field_data, all_data): 700 739 try: … … 709 748 html2python = staticmethod(html2python) 710 749 711 750 class SmallIntegerField(IntegerField): 712 def __init__(self, field_name, length=5, maxlength=5, is_required=False, validator_list=None ):751 def __init__(self, field_name, length=5, maxlength=5, is_required=False, validator_list=None, is_readonly=False): 713 752 if validator_list is None: validator_list = [] 714 753 validator_list = [self.isSmallInteger] + validator_list 715 IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list )754 IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list, readonly) 716 755 717 756 def isSmallInteger(self, field_data, all_data): 718 757 if not -32768 <= int(field_data) <= 32767: 719 758 raise validators.CriticalValidationError, gettext("Enter a whole number between -32,768 and 32,767.") 720 759 721 760 class PositiveIntegerField(IntegerField): 722 def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=None ):761 def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=None, is_readonly=False): 723 762 if validator_list is None: validator_list = [] 724 763 validator_list = [self.isPositive] + validator_list 725 IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list )764 IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list, readonly) 726 765 727 766 def isPositive(self, field_data, all_data): 728 767 if int(field_data) < 0: 729 768 raise validators.CriticalValidationError, gettext("Enter a positive number.") 730 769 731 770 class PositiveSmallIntegerField(IntegerField): 732 def __init__(self, field_name, length=5, maxlength=None, is_required=False, validator_list=None ):771 def __init__(self, field_name, length=5, maxlength=None, is_required=False, validator_list=None, is_readonly=False): 733 772 if validator_list is None: validator_list = [] 734 773 validator_list = [self.isPositiveSmall] + validator_list 735 IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list )774 IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list, readonly) 736 775 737 776 def isPositiveSmall(self, field_data, all_data): 738 777 if not 0 <= int(field_data) <= 32767: 739 778 raise validators.CriticalValidationError, gettext("Enter a whole number between 0 and 32,767.") 740 779 741 780 class FloatField(TextField): 742 def __init__(self, field_name, max_digits, decimal_places, is_required=False, validator_list=None ):781 def __init__(self, field_name, max_digits, decimal_places, is_required=False, validator_list=None, is_readonly=False): 743 782 if validator_list is None: validator_list = [] 744 783 self.max_digits, self.decimal_places = max_digits, decimal_places 745 784 validator_list = [self.isValidFloat] + validator_list 746 TextField.__init__(self, field_name, max_digits+1, max_digits+1, is_required, validator_list )785 TextField.__init__(self, field_name, max_digits+1, max_digits+1, is_required, validator_list, readonly) 747 786 748 787 def isValidFloat(self, field_data, all_data): 749 788 v = validators.IsValidFloat(self.max_digits, self.decimal_places) … … 765 804 class DatetimeField(TextField): 766 805 """A FormField that automatically converts its data to a datetime.datetime object. 767 806 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 ):807 def __init__(self, field_name, length=30, maxlength=None, is_required=False, validator_list=None, is_readonly=False): 769 808 if validator_list is None: validator_list = [] 770 809 self.field_name = field_name 771 810 self.length, self.maxlength = length, maxlength 772 811 self.is_required = is_required 773 812 self.validator_list = [validators.isValidANSIDatetime] + validator_list 813 self.is_readonly = is_readonly 774 814 775 815 def html2python(data): 776 816 "Converts the field into a datetime.datetime object" … … 792 832 class DateField(TextField): 793 833 """A FormField that automatically converts its data to a datetime.date object. 794 834 The data should be in the format YYYY-MM-DD.""" 795 def __init__(self, field_name, is_required=False, validator_list=None ):835 def __init__(self, field_name, is_required=False, validator_list=None, is_readonly=False): 796 836 if validator_list is None: validator_list = [] 797 837 validator_list = [self.isValidDate] + validator_list 798 838 TextField.__init__(self, field_name, length=10, maxlength=10, 799 is_required=is_required, validator_list=validator_list )839 is_required=is_required, validator_list=validator_list, is_readonly=is_readonly) 800 840 801 841 def isValidDate(self, field_data, all_data): 802 842 try: … … 817 857 class TimeField(TextField): 818 858 """A FormField that automatically converts its data to a datetime.time object. 819 859 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 ):860 def __init__(self, field_name, is_required=False, validator_list=None, is_readonly=False): 821 861 if validator_list is None: validator_list = [] 822 862 validator_list = [self.isValidTime] + validator_list 823 863 TextField.__init__(self, field_name, length=8, maxlength=8, 824 is_required=is_required, validator_list=validator_list )864 is_required=is_required, validator_list=validator_list, is_readonly=is_readonly) 825 865 826 866 def isValidTime(self, field_data, all_data): 827 867 try: … … 852 892 853 893 class EmailField(TextField): 854 894 "A convenience FormField for validating e-mail addresses" 855 def __init__(self, field_name, length=50, maxlength=75, is_required=False, validator_list=None ):895 def __init__(self, field_name, length=50, maxlength=75, is_required=False, validator_list=None, is_readonly=False): 856 896 if validator_list is None: validator_list = [] 857 897 validator_list = [self.isValidEmail] + validator_list 858 898 TextField.__init__(self, field_name, length, maxlength=maxlength, 859 is_required=is_required, validator_list=validator_list )899 is_required=is_required, validator_list=validator_list, is_readonly=is_readonly) 860 900 861 901 def isValidEmail(self, field_data, all_data): 862 902 try: … … 866 906 867 907 class URLField(TextField): 868 908 "A convenience FormField for validating URLs" 869 def __init__(self, field_name, length=50, maxlength=200, is_required=False, validator_list=None ):909 def __init__(self, field_name, length=50, maxlength=200, is_required=False, validator_list=None, is_readonly=False): 870 910 if validator_list is None: validator_list = [] 871 911 validator_list = [self.isValidURL] + validator_list 872 912 TextField.__init__(self, field_name, length=length, maxlength=maxlength, 873 is_required=is_required, validator_list=validator_list )913 is_required=is_required, validator_list=validator_list, is_readonly=is_readonly) 874 914 875 915 def isValidURL(self, field_data, all_data): 876 916 try: … … 879 919 raise validators.CriticalValidationError, e.messages 880 920 881 921 class IPAddressField(TextField): 882 def __init__(self, field_name, length=15, maxlength=15, is_required=False, validator_list=None ):922 def __init__(self, field_name, length=15, maxlength=15, is_required=False, validator_list=None, is_readonly=False): 883 923 if validator_list is None: validator_list = [] 884 924 validator_list = [self.isValidIPAddress] + validator_list 885 925 TextField.__init__(self, field_name, length=length, maxlength=maxlength, 886 is_required=is_required, validator_list=validator_list )926 is_required=is_required, validator_list=validator_list, is_readonly=is_readonly) 887 927 888 928 def isValidIPAddress(self, field_data, all_data): 889 929 try: … … 901 941 902 942 class FilePathField(SelectField): 903 943 "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 ):944 def __init__(self, field_name, path, match=None, recursive=False, is_required=False, validator_list=None, is_readonly=False): 905 945 import os 906 946 from django.db.models import BLANK_CHOICE_DASH 907 947 if match is not None: … … 921 961 choices.append((full_file, f)) 922 962 except OSError: 923 963 pass 924 SelectField.__init__(self, field_name, choices, 1, is_required, validator_list )964 SelectField.__init__(self, field_name, choices, 1, is_required, validator_list, readonly) 925 965 926 966 class PhoneNumberField(TextField): 927 967 "A convenience FormField for validating phone numbers (e.g. '630-555-1234')" 928 def __init__(self, field_name, is_required=False, validator_list=None ):968 def __init__(self, field_name, is_required=False, validator_list=None, is_readonly=False): 929 969 if validator_list is None: validator_list = [] 930 970 validator_list = [self.isValidPhone] + validator_list 931 971 TextField.__init__(self, field_name, length=12, maxlength=12, 932 is_required=is_required, validator_list=validator_list )972 is_required=is_required, validator_list=validator_list, is_readonly=is_readonly) 933 973 934 974 def isValidPhone(self, field_data, all_data): 935 975 try: … … 939 979 940 980 class USStateField(TextField): 941 981 "A convenience FormField for validating U.S. states (e.g. 'IL')" 942 def __init__(self, field_name, is_required=False, validator_list=None ):982 def __init__(self, field_name, is_required=False, validator_list=None, is_readonly=False): 943 983 if validator_list is None: validator_list = [] 944 984 validator_list = [self.isValidUSState] + validator_list 945 985 TextField.__init__(self, field_name, length=2, maxlength=2, 946 is_required=is_required, validator_list=validator_list )986 is_required=is_required, validator_list=validator_list, is_readonly=is_readonly) 947 987 948 988 def isValidUSState(self, field_data, all_data): 949 989 try: … … 960 1000 961 1001 class CommaSeparatedIntegerField(TextField): 962 1002 "A convenience FormField for validating comma-separated integer fields" 963 def __init__(self, field_name, maxlength=None, is_required=False, validator_list=None ):1003 def __init__(self, field_name, maxlength=None, is_required=False, validator_list=None, is_readonly=False): 964 1004 if validator_list is None: validator_list = [] 965 1005 validator_list = [self.isCommaSeparatedIntegerList] + validator_list 966 1006 TextField.__init__(self, field_name, length=20, maxlength=maxlength, 967 is_required=is_required, validator_list=validator_list )1007 is_required=is_required, validator_list=validator_list, is_readonly=is_readonly) 968 1008 969 1009 def isCommaSeparatedIntegerList(self, field_data, all_data): 970 1010 try: