Ticket #1714: readonly_fields_v2.2.patch

File readonly_fields_v2.2.patch, 25.4 KB (added by SmileyChris, 9 years ago)

spell check, small bug fix, and models integration

  • django/db/models/fields/__init__.py

     
    264264            if core_field_names:
    265265                params['validator_list'].append(validators.RequiredIfOtherFieldsGiven(core_field_names, gettext_lazy("This field is required.")))
    266266
     267        # If this field is not editable, then use the readonly attribute FormFields.
     268        params['is_readonly'] = not self.editable
     269
    267270        # Finally, add the field_names.
    268271        field_names = self.get_manipulator_field_names(name_prefix)
    269272        return [man(field_name=field_names[i], **params) for i, man in enumerate(field_objs)]
  • 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.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)
    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.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
    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, is_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.is_readonly = is_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, is_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.is_readonly = is_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):
    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):
    428462        if validator_list is None: validator_list = []
    429463        self.field_name, self.is_required = field_name, is_required
    430464        self.validator_list = validator_list[:]
     465        self.is_readonly = is_readonly
    431466
    432467    def render(self, data):
    433468        return '<input type="hidden" id="%s" name="%s" value="%s" />' % \
    434469            (self.get_id(), self.field_name, escape(data))
    435470
    436471class 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):
    438473        if validator_list is None: validator_list = []
    439474        self.field_name = field_name
    440475        self.checked_by_default = checked_by_default
    441476        self.is_required = False # because the validator looks for these
    442477        self.validator_list = validator_list[:]
     478        self.is_readonly = is_readonly
    443479
    444480    def render(self, data):
    445481        checked_html = ''
    446482        if data or (data is '' and self.checked_by_default):
    447483            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())
    451487
    452488    def html2python(data):
    453489        "Convert value from browser ('on' or '') to a Python boolean"
     
    457493    html2python = staticmethod(html2python)
    458494
    459495class 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):
    461497        if validator_list is None: validator_list = []
    462498        if choices is None: choices = []
    463499        self.field_name = field_name
     
    466502        self.validator_list = [self.isValidChoice] + validator_list
    467503        if member_name != None:
    468504            self.member_name = member_name
     505        self.is_readonly = is_readonly
    469506
    470507    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())]
    474511        str_data = str(data) # normalize to string
    475512        for value, display_name in self.choices:
    476513            selected_html = ''
     
    495532    html2python = staticmethod(html2python)
    496533
    497534class 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):
    499536        if validator_list is None: validator_list = []
    500537        if choices is None: choices = []
    501538        self.field_name = field_name
     
    505542        self.ul_class = ul_class
    506543        if member_name != None:
    507544            self.member_name = member_name
     545        self.is_readonly = is_readonly
    508546
    509547    def render(self, data):
    510548        """
     
    546584            datalist.append({
    547585                'value': value,
    548586                '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()),
    551589                'label': '<label for="%s">%s</label>' % \
    552590                    (self.get_id() + '_' + str(i), display_name),
    553591            })
     
    561599
    562600class NullBooleanField(SelectField):
    563601    "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):
    565603        if validator_list is None: validator_list = []
    566604        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)
    568606
    569607    def render(self, data):
    570608        if data is None: data = '1'
     
    579617class SelectMultipleField(SelectField):
    580618    requires_data_list = True
    581619    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())]
    585623        str_data_list = map(str, data) # normalize to strings
    586624        for value, choice in self.choices:
    587625            selected_html = ''
     
    615653    back into the single list that validators, renderers and save() expect.
    616654    """
    617655    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):
    619657        if validator_list is None: validator_list = []
    620658        if choices is None: choices = []
    621659        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)
    623661
    624662    def prepare(self, new_data):
    625663        # new_data has "split" this field into several fields, so flatten it
     
    638676            if str(value) in str_data_list:
    639677                checked_html = ' checked="checked"'
    640678            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(),
    643681                self.get_id() + value, choice))
    644682        output.append('</ul>')
    645683        return '\n'.join(output)
     
    649687####################
    650688
    651689class 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):
    653691        if validator_list is None: validator_list = []
    654692        self.field_name, self.is_required = field_name, is_required
    655693        self.validator_list = [self.isNonEmptyFile] + validator_list
     694        self.is_readonly = is_readonly
    656695
    657696    def isNonEmptyFile(self, field_data, all_data):
    658697        try:
     
    663702            raise validators.CriticalValidationError, gettext("The submitted file is empty.")
    664703
    665704    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())
    668707
    669708    def html2python(data):
    670709        if data is None:
     
    689728####################
    690729
    691730class 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):
    693732        if validator_list is None: validator_list = []
    694733        validator_list = [self.isInteger] + validator_list
    695734        if member_name is not None:
    696735            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)
    698737
    699738    def isInteger(self, field_data, all_data):
    700739        try:
     
    709748    html2python = staticmethod(html2python)
    710749
    711750class 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):
    713752        if validator_list is None: validator_list = []
    714753        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)
    716755
    717756    def isSmallInteger(self, field_data, all_data):
    718757        if not -32768 <= int(field_data) <= 32767:
    719758            raise validators.CriticalValidationError, gettext("Enter a whole number between -32,768 and 32,767.")
    720759
    721760class 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):
    723762        if validator_list is None: validator_list = []
    724763        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)
    726765
    727766    def isPositive(self, field_data, all_data):
    728767        if int(field_data) < 0:
    729768            raise validators.CriticalValidationError, gettext("Enter a positive number.")
    730769
    731770class 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):
    733772        if validator_list is None: validator_list = []
    734773        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)
    736775
    737776    def isPositiveSmall(self, field_data, all_data):
    738777        if not 0 <= int(field_data) <= 32767:
    739778            raise validators.CriticalValidationError, gettext("Enter a whole number between 0 and 32,767.")
    740779
    741780class 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):
    743782        if validator_list is None: validator_list = []
    744783        self.max_digits, self.decimal_places = max_digits, decimal_places
    745784        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)
    747786
    748787    def isValidFloat(self, field_data, all_data):
    749788        v = validators.IsValidFloat(self.max_digits, self.decimal_places)
     
    765804class DatetimeField(TextField):
    766805    """A FormField that automatically converts its data to a datetime.datetime object.
    767806    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):
    769808        if validator_list is None: validator_list = []
    770809        self.field_name = field_name
    771810        self.length, self.maxlength = length, maxlength
    772811        self.is_required = is_required
    773812        self.validator_list = [validators.isValidANSIDatetime] + validator_list
     813        self.is_readonly = is_readonly
    774814
    775815    def html2python(data):
    776816        "Converts the field into a datetime.datetime object"
     
    792832class DateField(TextField):
    793833    """A FormField that automatically converts its data to a datetime.date object.
    794834    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):
    796836        if validator_list is None: validator_list = []
    797837        validator_list = [self.isValidDate] + validator_list
    798838        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)
    800840
    801841    def isValidDate(self, field_data, all_data):
    802842        try:
     
    817857class TimeField(TextField):
    818858    """A FormField that automatically converts its data to a datetime.time object.
    819859    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):
    821861        if validator_list is None: validator_list = []
    822862        validator_list = [self.isValidTime] + validator_list
    823863        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)
    825865
    826866    def isValidTime(self, field_data, all_data):
    827867        try:
     
    852892
    853893class EmailField(TextField):
    854894    "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):
    856896        if validator_list is None: validator_list = []
    857897        validator_list = [self.isValidEmail] + validator_list
    858898        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)
    860900
    861901    def isValidEmail(self, field_data, all_data):
    862902        try:
     
    866906
    867907class URLField(TextField):
    868908    "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):
    870910        if validator_list is None: validator_list = []
    871911        validator_list = [self.isValidURL] + validator_list
    872912        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)
    874914
    875915    def isValidURL(self, field_data, all_data):
    876916        try:
     
    879919            raise validators.CriticalValidationError, e.messages
    880920
    881921class 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):
    883923        if validator_list is None: validator_list = []
    884924        validator_list = [self.isValidIPAddress] + validator_list
    885925        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)
    887927
    888928    def isValidIPAddress(self, field_data, all_data):
    889929        try:
     
    901941
    902942class FilePathField(SelectField):
    903943    "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):
    905945        import os
    906946        from django.db.models import BLANK_CHOICE_DASH
    907947        if match is not None:
     
    921961                        choices.append((full_file, f))
    922962            except OSError:
    923963                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)
    925965
    926966class PhoneNumberField(TextField):
    927967    "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):
    929969        if validator_list is None: validator_list = []
    930970        validator_list = [self.isValidPhone] + validator_list
    931971        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)
    933973
    934974    def isValidPhone(self, field_data, all_data):
    935975        try:
     
    939979
    940980class USStateField(TextField):
    941981    "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):
    943983        if validator_list is None: validator_list = []
    944984        validator_list = [self.isValidUSState] + validator_list
    945985        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)
    947987
    948988    def isValidUSState(self, field_data, all_data):
    949989        try:
     
    9601000
    9611001class CommaSeparatedIntegerField(TextField):
    9621002    "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):
    9641004        if validator_list is None: validator_list = []
    9651005        validator_list = [self.isCommaSeparatedIntegerList] + validator_list
    9661006        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)
    9681008
    9691009    def isCommaSeparatedIntegerList(self, field_data, all_data):
    9701010        try:
Back to Top