Ticket #1714: readonly_fields_v2.patch

File readonly_fields_v2.patch, 24.1 KB (added by SmileyChris, 9 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:
Back to Top