Code

Ticket #1714: readonly_fields_v2.patch

File readonly_fields_v2.patch, 24.1 KB (added by SmileyChris, 8 years ago)

Newer patch, building on mir@noris' patch

  • django/forms/__init__.py

     
    360360    def get_id(self): 
    361361        "Returns the HTML 'id' attribute for this form field." 
    362362        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) 
    363378 
     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 
    364396#################### 
    365397# GENERIC WIDGETS  # 
    366398#################### 
    367399 
    368400class TextField(FormField): 
    369401    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): 
    371403        if validator_list is None: validator_list = [] 
    372404        self.field_name = field_name 
    373405        self.length, self.maxlength = length, maxlength 
     
    375407        self.validator_list = [self.isValidLength, self.hasNoNewlines] + validator_list 
    376408        if member_name != None: 
    377409            self.member_name = member_name 
     410        self.readonly = readonly 
    378411 
    379412    def isValidLength(self, data, form): 
    380413        if data and self.maxlength and len(data.decode(settings.DEFAULT_CHARSET)) > self.maxlength: 
     
    390423            data = '' 
    391424        maxlength = '' 
    392425        if self.maxlength: 
    393             maxlength = 'maxlength="%s" ' % self.maxlength 
     426            maxlength = ' maxlength="%s"' % self.maxlength 
    394427        if isinstance(data, unicode): 
    395428            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()) 
    399432 
    400433    def html2python(data): 
    401434        return data 
     
    405438    input_type = "password" 
    406439 
    407440class 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): 
    409442        if validator_list is None: validator_list = [] 
    410443        self.field_name = field_name 
     444        self.validator_list = validator_list[:] 
    411445        self.rows, self.cols, self.is_required = rows, cols, is_required 
    412         self.validator_list = validator_list[:] 
    413446        if maxlength: 
    414447            self.validator_list.append(self.isValidLength) 
    415448            self.maxlength = maxlength 
     449        self.readonly = readonly 
    416450 
    417451    def render(self, data): 
    418452        if data is None: 
    419453            data = '' 
    420454        if isinstance(data, unicode): 
    421455            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)) 
    425459 
    426460class HiddenField(FormField): 
    427461    def __init__(self, field_name, is_required=False, validator_list=None): 
     
    434468            (self.get_id(), self.field_name, escape(data)) 
    435469 
    436470class 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): 
    438472        if validator_list is None: validator_list = [] 
    439473        self.field_name = field_name 
    440474        self.checked_by_default = checked_by_default 
    441475        self.is_required = False # because the validator looks for these 
    442476        self.validator_list = validator_list[:] 
     477        self.readonly = readonly 
    443478 
    444479    def render(self, data): 
    445480        checked_html = '' 
    446481        if data or (data is '' and self.checked_by_default): 
    447482            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()) 
    451486 
    452487    def html2python(data): 
    453488        "Convert value from browser ('on' or '') to a Python boolean" 
     
    457492    html2python = staticmethod(html2python) 
    458493 
    459494class 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): 
    461496        if validator_list is None: validator_list = [] 
    462497        if choices is None: choices = [] 
    463498        self.field_name = field_name 
     
    466501        self.validator_list = [self.isValidChoice] + validator_list 
    467502        if member_name != None: 
    468503            self.member_name = member_name 
     504        self.readonly = readonly 
    469505 
    470506    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())] 
    474510        str_data = str(data) # normalize to string 
    475511        for value, display_name in self.choices: 
    476512            selected_html = '' 
     
    495531    html2python = staticmethod(html2python) 
    496532 
    497533class 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): 
    499535        if validator_list is None: validator_list = [] 
    500536        if choices is None: choices = [] 
    501537        self.field_name = field_name 
     
    505541        self.ul_class = ul_class 
    506542        if member_name != None: 
    507543            self.member_name = member_name 
     544        self.readonly = readonly 
    508545 
    509546    def render(self, data): 
    510547        """ 
     
    546583            datalist.append({ 
    547584                'value': value, 
    548585                '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()), 
    551588                'label': '<label for="%s">%s</label>' % \ 
    552589                    (self.get_id() + '_' + str(i), display_name), 
    553590            }) 
     
    561598 
    562599class NullBooleanField(SelectField): 
    563600    "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): 
    565602        if validator_list is None: validator_list = [] 
    566603        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) 
    568605 
    569606    def render(self, data): 
    570607        if data is None: data = '1' 
     
    579616class SelectMultipleField(SelectField): 
    580617    requires_data_list = True 
    581618    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())] 
    585622        str_data_list = map(str, data) # normalize to strings 
    586623        for value, choice in self.choices: 
    587624            selected_html = '' 
     
    615652    back into the single list that validators, renderers and save() expect. 
    616653    """ 
    617654    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): 
    619656        if validator_list is None: validator_list = [] 
    620657        if choices is None: choices = [] 
    621658        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) 
    623660 
    624661    def prepare(self, new_data): 
    625662        # new_data has "split" this field into several fields, so flatten it 
     
    638675            if str(value) in str_data_list: 
    639676                checked_html = ' checked="checked"' 
    640677            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(), 
    643680                self.get_id() + value, choice)) 
    644681        output.append('</ul>') 
    645682        return '\n'.join(output) 
     
    649686#################### 
    650687 
    651688class 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): 
    653690        if validator_list is None: validator_list = [] 
    654691        self.field_name, self.is_required = field_name, is_required 
    655692        self.validator_list = [self.isNonEmptyFile] + validator_list 
     693        self.readonly = readonly 
    656694 
    657695    def isNonEmptyFile(self, field_data, all_data): 
    658696        try: 
     
    663701            raise validators.CriticalValidationError, gettext("The submitted file is empty.") 
    664702 
    665703    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()) 
    668706 
    669707    def html2python(data): 
    670708        if data is None: 
     
    689727#################### 
    690728 
    691729class 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): 
    693731        if validator_list is None: validator_list = [] 
    694732        validator_list = [self.isInteger] + validator_list 
    695733        if member_name is not None: 
    696734            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) 
    698736 
    699737    def isInteger(self, field_data, all_data): 
    700738        try: 
     
    709747    html2python = staticmethod(html2python) 
    710748 
    711749class 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): 
    713751        if validator_list is None: validator_list = [] 
    714752        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) 
    716754 
    717755    def isSmallInteger(self, field_data, all_data): 
    718756        if not -32768 <= int(field_data) <= 32767: 
    719757            raise validators.CriticalValidationError, gettext("Enter a whole number between -32,768 and 32,767.") 
    720758 
    721759class 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): 
    723761        if validator_list is None: validator_list = [] 
    724762        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) 
    726764 
    727765    def isPositive(self, field_data, all_data): 
    728766        if int(field_data) < 0: 
    729767            raise validators.CriticalValidationError, gettext("Enter a positive number.") 
    730768 
    731769class 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): 
    733771        if validator_list is None: validator_list = [] 
    734772        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) 
    736774 
    737775    def isPositiveSmall(self, field_data, all_data): 
    738776        if not 0 <= int(field_data) <= 32767: 
    739777            raise validators.CriticalValidationError, gettext("Enter a whole number between 0 and 32,767.") 
    740778 
    741779class 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): 
    743781        if validator_list is None: validator_list = [] 
    744782        self.max_digits, self.decimal_places = max_digits, decimal_places 
    745783        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) 
    747785 
    748786    def isValidFloat(self, field_data, all_data): 
    749787        v = validators.IsValidFloat(self.max_digits, self.decimal_places) 
     
    765803class DatetimeField(TextField): 
    766804    """A FormField that automatically converts its data to a datetime.datetime object. 
    767805    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): 
    769807        if validator_list is None: validator_list = [] 
    770808        self.field_name = field_name 
    771809        self.length, self.maxlength = length, maxlength 
    772810        self.is_required = is_required 
    773811        self.validator_list = [validators.isValidANSIDatetime] + validator_list 
     812        self.readonly = readonly 
    774813 
    775814    def html2python(data): 
    776815        "Converts the field into a datetime.datetime object" 
     
    792831class DateField(TextField): 
    793832    """A FormField that automatically converts its data to a datetime.date object. 
    794833    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): 
    796835        if validator_list is None: validator_list = [] 
    797836        validator_list = [self.isValidDate] + validator_list 
    798837        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) 
    800839 
    801840    def isValidDate(self, field_data, all_data): 
    802841        try: 
     
    817856class TimeField(TextField): 
    818857    """A FormField that automatically converts its data to a datetime.time object. 
    819858    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): 
    821860        if validator_list is None: validator_list = [] 
    822861        validator_list = [self.isValidTime] + validator_list 
    823862        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) 
    825864 
    826865    def isValidTime(self, field_data, all_data): 
    827866        try: 
     
    852891 
    853892class EmailField(TextField): 
    854893    "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): 
    856895        if validator_list is None: validator_list = [] 
    857896        validator_list = [self.isValidEmail] + validator_list 
    858897        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) 
    860899 
    861900    def isValidEmail(self, field_data, all_data): 
    862901        try: 
     
    866905 
    867906class URLField(TextField): 
    868907    "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): 
    870909        if validator_list is None: validator_list = [] 
    871910        validator_list = [self.isValidURL] + validator_list 
    872911        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) 
    874913 
    875914    def isValidURL(self, field_data, all_data): 
    876915        try: 
     
    879918            raise validators.CriticalValidationError, e.messages 
    880919 
    881920class 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): 
    883922        if validator_list is None: validator_list = [] 
    884923        validator_list = [self.isValidIPAddress] + validator_list 
    885924        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) 
    887926 
    888927    def isValidIPAddress(self, field_data, all_data): 
    889928        try: 
     
    901940 
    902941class FilePathField(SelectField): 
    903942    "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): 
    905944        import os 
    906945        from django.db.models import BLANK_CHOICE_DASH 
    907946        if match is not None: 
     
    921960                        choices.append((full_file, f)) 
    922961            except OSError: 
    923962                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) 
    925964 
    926965class PhoneNumberField(TextField): 
    927966    "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): 
    929968        if validator_list is None: validator_list = [] 
    930969        validator_list = [self.isValidPhone] + validator_list 
    931970        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) 
    933972 
    934973    def isValidPhone(self, field_data, all_data): 
    935974        try: 
     
    939978 
    940979class USStateField(TextField): 
    941980    "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): 
    943982        if validator_list is None: validator_list = [] 
    944983        validator_list = [self.isValidUSState] + validator_list 
    945984        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) 
    947986 
    948987    def isValidUSState(self, field_data, all_data): 
    949988        try: 
     
    960999 
    9611000class CommaSeparatedIntegerField(TextField): 
    9621001    "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): 
    9641003        if validator_list is None: validator_list = [] 
    9651004        validator_list = [self.isCommaSeparatedIntegerList] + validator_list 
    9661005        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) 
    9681007 
    9691008    def isCommaSeparatedIntegerList(self, field_data, all_data): 
    9701009        try: