Ticket #6630: 00-newforms-fieldsets.diff
File 00-newforms-fieldsets.diff, 48.3 KB (added by , 17 years ago) |
---|
-
django/newforms/extras/widgets.py
=== modified file 'django/newforms/extras/widgets.py'
21 21 day_field = '%s_day' 22 22 year_field = '%s_year' 23 23 24 def __init__(self, attrs=None, years=None):24 def __init__(self, attrs=None, row_attrs=None, years=None): 25 25 # years is an optional list/tuple of years to use in the "year" select box. 26 s elf.attrs = attrs or {}26 super(SelectDateWidget, self).__init__(attrs, row_attrs) 27 27 if years: 28 28 self.years = years 29 29 else: -
django/newforms/fields.py
=== modified file 'django/newforms/fields.py'
513 513 return value 514 514 if self.verify_exists: 515 515 import urllib2 516 from django.conf import settings517 516 headers = { 518 517 "Accept": "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5", 519 518 "Accept-Language": "en-us,en;q=0.5", -
django/newforms/forms.py
=== modified file 'django/newforms/forms.py'
4 4 5 5 from copy import deepcopy 6 6 7 from django.utils.datastructures import SortedDict 8 from django.utils.html import escape7 from django.utils.datastructures import SortedDict, InheritableOptions 8 from django.utils.html import conditional_escape 9 9 from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode 10 10 from django.utils.safestring import mark_safe 11 11 … … 22 22 name = name[0].upper() + name[1:] 23 23 return name.replace('_', ' ') 24 24 25 def get_declared_fields(bases, attrs, with_base_fields=True): 26 """ 27 Create a list of form field instances from the passed in 'attrs', plus any 28 similar fields on the base classes (in 'bases'). This is used by both the 29 Form and ModelForm metclasses. 30 31 If 'with_base_fields' is True, all fields from the bases are used. 32 Otherwise, only fields in the 'declared_fields' attribute on the bases are 33 used. The distinction is useful in ModelForm subclassing. 34 """ 35 fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)] 36 fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter)) 37 38 # If this class is subclassing another Form, add that Form's fields. 39 # Note that we loop over the bases in *reverse*. This is necessary in 40 # order to preserve the correct order of fields. 41 if with_base_fields: 42 for base in bases[::-1]: 43 if hasattr(base, 'base_fields'): 44 fields = base.base_fields.items() + fields 45 else: 46 for base in bases[::-1]: 47 if hasattr(base, 'declared_fields'): 48 fields = base.declared_fields.items() + fields 49 50 return SortedDict(fields) 51 52 class DeclarativeFieldsMetaclass(type): 53 """ 54 Metaclass that converts Field attributes to a dictionary called 55 'base_fields', taking into account parent class 'base_fields' as well. 56 """ 25 class FormOptions(InheritableOptions): 26 _default_options = { 27 'fieldsets': None, 28 'fields': None, 29 'exclude': None, 30 } 31 32 class FormMetaclass(type): 33 34 def create_options(cls, new_cls): 35 new_cls._meta = new_cls.options(new_cls) 36 try: 37 delattr(new_cls, 'Meta') 38 except AttributeError: 39 pass 40 create_options = classmethod(create_options) 41 42 def create_declared_fields(cls, new_cls): 43 fields = [] 44 for name, attr in new_cls.__dict__.items(): 45 if isinstance(attr, Field): 46 fields.append((name, attr)) 47 delattr(new_cls, name) 48 fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter)) 49 new_cls._declared_fields = SortedDict(fields) 50 create_declared_fields = classmethod(create_declared_fields) 51 52 def create_base_fields_pool_from_declared_fields(cls, new_cls): 53 fields = [] 54 # Add all declared fields from this class and from superclasses. Note 55 # that we loop over the bases in *reverse*. This is necessary 56 # in order to preserve the correct order of fields. 57 for base in new_cls.__mro__[::-1]: 58 try: 59 fields += base._declared_fields.items() 60 except AttributeError: 61 pass 62 new_cls._base_fields_pool = SortedDict(fields) 63 create_base_fields_pool_from_declared_fields = classmethod(create_base_fields_pool_from_declared_fields) 64 65 def create_base_fields_from_base_fields_pool(cls, new_cls): 66 if new_cls._meta.fieldsets: 67 names = [] 68 for fieldset in new_cls._meta.fieldsets: 69 names.extend(fieldset['fields']) 70 elif new_cls._meta.fields: 71 names = new_cls._meta.fields 72 elif new_cls._meta.exclude: 73 names = [name for name in new_cls._base_fields_pool if name not in new_cls._meta.exclude] 74 else: 75 names = new_cls._base_fields_pool.keys() 76 new_cls.base_fields = SortedDict([(name, new_cls._base_fields_pool[name]) for name in names]) 77 create_base_fields_from_base_fields_pool = classmethod(create_base_fields_from_base_fields_pool) 78 57 79 def __new__(cls, name, bases, attrs): 58 attrs['base_fields'] = get_declared_fields(bases, attrs) 59 return type.__new__(cls, name, bases, attrs) 80 new_cls = type.__new__(cls, name, bases, attrs) 81 cls.create_options(new_cls) 82 cls.create_declared_fields(new_cls) 83 cls.create_base_fields_pool_from_declared_fields(new_cls) 84 cls.create_base_fields_from_base_fields_pool(new_cls) 85 return new_cls 60 86 61 87 class BaseForm(StrAndUnicode): 62 88 # This is the main implementation of all the Form logic. Note that this … … 98 124 return BoundField(self, field, name) 99 125 100 126 def _get_errors(self): 101 "Returns an ErrorDict for the data provided for the form "127 "Returns an ErrorDict for the data provided for the form." 102 128 if self._errors is None: 103 129 self.full_clean() 104 130 return self._errors … … 111 137 """ 112 138 return self.is_bound and not bool(self.errors) 113 139 114 def add_prefix(self, field_name): 140 def has_fieldsets(self): 141 "Returns True if this form has fieldsets." 142 return bool(self._meta.fieldsets) 143 144 def add_prefix(self, name): 115 145 """ 116 146 Returns the field name with a prefix appended, if this Form has a 117 147 prefix set. 118 148 119 149 Subclasses may wish to override. 120 150 """ 121 return self.prefix and ('%s-%s' % (self.prefix, field_name)) or field_name 122 123 def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_on_separate_row): 151 return self.prefix and ('%s-%s' % (self.prefix, name)) or name 152 153 def first_fieldset_attributes(self): 154 "Returns attributes for first fieldset as HTML code." 155 if self.has_fieldsets() and 'attrs' in self._meta.fieldsets[0]: 156 return flatatt(self._meta.fieldsets[0]['attrs']) 157 else: 158 return u'' 159 160 def first_fieldset_legend_tag(self): 161 "Returns legend tag for first fieldset as HTML code." 162 if self.has_fieldsets() and 'legend' in self._meta.fieldsets[0]: 163 return mark_safe(u'<legend>%s</legend>' % conditional_escape(force_unicode(self._meta.fieldsets[0]['legend']))) 164 else: 165 return u'' 166 167 def top_errors_html_output(self, top_errors, top_errors_row): 168 "Helper function for outputting HTML from a top errors row. Used by _html_output." 169 return top_errors_row % top_errors 170 171 def fieldset_html_output(self, fieldset, fields, fieldset_start, fieldset_end, is_first, is_last): 172 "Helper function for outputting HTML from a fieldset. Used by _html_output." 173 output = [] 174 if fieldset_start and not is_first: 175 fieldset_attrs = flatatt(fieldset.get('attrs', {})) 176 if 'legend' in fieldset: 177 legend_tag = u'\n<legend>%s</legend>' % conditional_escape(force_unicode(fieldset['legend'])) 178 else: 179 legend_tag = u'' 180 output.append(fieldset_start % (u'<fieldset%s>%s' % (fieldset_attrs, legend_tag))) 181 for name in fieldset['fields']: 182 output.append(fields[name]) 183 if fieldset_end and not is_last: 184 output.append(fieldset_end % u'</fieldset>') 185 return u'\n'.join(output) 186 187 def hidden_fields_html_output(self, hidden_fields, hidden_fields_row): 188 "Helper function for outputting HTML from a hidden fields row. Used by _html_output." 189 return hidden_fields_row % u''.join(hidden_fields) 190 191 def _html_output(self, output_type, top_errors_row, fieldset_start, fieldset_end, hidden_fields_row): 124 192 "Helper function for outputting HTML. Used by as_table(), as_ul(), as_p()." 193 output = [] 125 194 top_errors = self.non_field_errors() # Errors that should be displayed above all fields. 126 output, hidden_fields = [], []195 hidden_fields, visible_fields = [], {} 127 196 for name, field in self.fields.items(): 128 197 bf = BoundField(self, field, name) 129 bf_errors = self.error_class([escape(error) for error in bf.errors]) # Escape and cache in local variable.130 198 if bf.is_hidden: 131 if bf _errors:132 top_errors.extend(['(Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_errors])199 if bf.errors: 200 top_errors.extend(['(Hidden field %s) %s' % (name, conditional_escape(force_unicode(e))) for e in bf.errors]) 133 201 hidden_fields.append(unicode(bf)) 134 202 else: 135 if errors_on_separate_row and bf_errors: 136 output.append(error_row % force_unicode(bf_errors)) 137 if bf.label: 138 label = escape(force_unicode(bf.label)) 139 # Only add the suffix if the label does not end in 140 # punctuation. 141 if self.label_suffix: 142 if label[-1] not in ':?.!': 143 label += self.label_suffix 144 label = bf.label_tag(label) or '' 145 else: 146 label = '' 147 if field.help_text: 148 help_text = help_text_html % force_unicode(field.help_text) 149 else: 150 help_text = u'' 151 output.append(normal_row % {'errors': force_unicode(bf_errors), 'label': force_unicode(label), 'field': unicode(bf), 'help_text': help_text}) 203 visible_fields[name] = getattr(bf.widget, 'for_%s' % output_type)(rendered_widget=unicode(bf), 204 rendered_errors=unicode(bf.errors), label_tag=bf.label_tag, help_text=bf.help_text, row_attrs=bf.row_attrs) 152 205 if top_errors: 153 output.insert(0, error_row % top_errors) 154 if hidden_fields: # Insert any hidden fields in the last row. 155 str_hidden = u''.join(hidden_fields) 156 if output: 157 last_row = output[-1] 158 # Chop off the trailing row_ender (e.g. '</td></tr>') and 159 # insert the hidden fields. 160 output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender 161 else: 162 # If there aren't any rows in the output, just append the 163 # hidden fields. 164 output.append(str_hidden) 206 output.append(self.top_errors_html_output(top_errors, top_errors_row)) 207 if self.has_fieldsets(): 208 for i, fieldset in enumerate(self._meta.fieldsets): 209 output_method = fieldset.get('html_output_method', self.__class__.fieldset_html_output) 210 fields = dict((name, visible_fields[name]) for name in fieldset['fields'] if name in visible_fields) 211 is_first = (i == 0) 212 is_last = (i + 1 == len(self._meta.fieldsets)) 213 output.append(output_method(self, fieldset, fields, fieldset_start, fieldset_end, is_first, is_last)) 214 else: 215 for name in self.fields: 216 if name in visible_fields: 217 output.append(visible_fields[name]) 218 if hidden_fields: 219 output.append(self.hidden_fields_html_output(hidden_fields, hidden_fields_row)) 165 220 return mark_safe(u'\n'.join(output)) 166 221 167 222 def as_table(self): 168 223 "Returns this form rendered as HTML <tr>s -- excluding the <table></table>." 169 return self._html_output( u'<tr><th>%(label)s</th><td>%(errors)s%(field)s%(help_text)s</td></tr>', u'<tr><td colspan="2">%s</td></tr>', '</td></tr>', u'<br />%s', False)224 return self._html_output('table', u'<tr><td colspan="2">%s</td></tr>', u'%s\n<table>', u'</table>\n%s', u'<tr><td colspan="2">%s</td></tr>') 170 225 171 226 def as_ul(self): 172 227 "Returns this form rendered as HTML <li>s -- excluding the <ul></ul>." 173 return self._html_output( u'<li>%(errors)s%(label)s %(field)s%(help_text)s</li>', u'<li>%s</li>', '</li>', u' %s', False)228 return self._html_output('ul', u'<li>%s</li>', u'%s\n<ul>', u'</ul>\n%s', u'<li>%s</li>') 174 229 175 230 def as_p(self): 176 231 "Returns this form rendered as HTML <p>s." 177 return self._html_output( u'<p>%(label)s %(field)s%(help_text)s</p>', u'%s', '</p>', u' %s', True)232 return self._html_output('p', u'%s', u'%s', u'%s', u'<p>%s</p>') 178 233 179 234 def non_field_errors(self): 180 235 """ … … 209 264 value = getattr(self, 'clean_%s' % name)() 210 265 self.cleaned_data[name] = value 211 266 except ValidationError, e: 212 self._errors[name] = e.messages267 self._errors[name] = self.error_class(e.messages) 213 268 if name in self.cleaned_data: 214 269 del self.cleaned_data[name] 215 270 try: 216 271 self.cleaned_data = self.clean() 217 272 except ValidationError, e: 218 self._errors[NON_FIELD_ERRORS] = e.messages273 self._errors[NON_FIELD_ERRORS] = self.error_class(e.messages) 219 274 if self._errors: 220 275 delattr(self, 'cleaned_data') 221 276 … … 245 300 # fancy metaclass stuff purely for the semantic sugar -- it allows one 246 301 # to define a form using declarative syntax. 247 302 # BaseForm itself has no way of designating self.fields. 248 __metaclass__ = DeclarativeFieldsMetaclass 303 __metaclass__ = FormMetaclass 304 options = FormOptions 249 305 250 306 class BoundField(StrAndUnicode): 251 307 "A Field plus data" 252 308 def __init__(self, form, field, name): 253 309 self.form = form 254 310 self.field = field 311 self.widget = field.widget 255 312 self.name = name 256 313 self.html_name = form.add_prefix(name) 257 if self.field.label is None:258 self.label = pretty_name(name)259 else:260 self.label = self.field.label261 self.help_text = field.help_text or ''262 314 263 315 def __unicode__(self): 264 316 """Renders this field as an HTML widget.""" … … 279 331 field's default widget will be used. 280 332 """ 281 333 if not widget: 282 widget = self. field.widget334 widget = self.widget 283 335 attrs = attrs or {} 284 336 auto_id = self.auto_id 285 337 if auto_id and 'id' not in attrs and 'id' not in widget.attrs: … … 312 364 """ 313 365 Returns the data for this BoundField, or None if it wasn't given. 314 366 """ 315 return self. field.widget.value_from_datadict(self.form.data, self.form.files, self.html_name)367 return self.widget.value_from_datadict(self.form.data, self.form.files, self.html_name) 316 368 data = property(_data) 317 369 318 def label_tag(self, contents=None, attrs=None): 319 """ 320 Wraps the given contents in a <label>, if the field has an ID attribute. 321 Does not HTML-escape the contents. If contents aren't given, uses the 322 field's HTML-escaped label. 323 324 If attrs are given, they're used as HTML attributes on the <label> tag. 325 """ 326 contents = contents or escape(self.label) 327 widget = self.field.widget 328 id_ = widget.attrs.get('id') or self.auto_id 329 if id_: 330 attrs = attrs and flatatt(attrs) or '' 331 contents = '<label for="%s"%s>%s</label>' % (widget.id_for_label(id_), attrs, contents) 332 return mark_safe(contents) 370 def _label(self): 371 "Returns label for this field as safe HTML code." 372 if self.field.label is None: 373 return pretty_name(self.name) 374 else: 375 return conditional_escape(force_unicode(self.field.label)) 376 label = property(_label) 377 378 def _label_tag(self): 379 "Returns label tag for this field as safe HTML code." 380 label = self.label 381 if self.form.label_suffix: 382 # Only add the suffix if the label does not end in punctuation. 383 if label and label[-1] not in ':?.!': 384 label += self.form.label_suffix 385 id_ = self.widget.attrs.get('id') or self.auto_id 386 if label and id_: 387 id_ = self.widget.id_for_label(id_) 388 return mark_safe(self.widget.label_tag(id_, label)) 389 else: 390 return label 391 label_tag = property(_label_tag) 392 393 def _help_text(self): 394 "Returns help text for this field as safe HTML code." 395 if self.field.help_text: 396 return force_unicode(self.field.help_text) 397 else: 398 return u'' 399 help_text = property(_help_text) 400 401 def _row_attrs(self): 402 "Returns row attributes for this field as safe HTML code." 403 return flatatt(self.widget.row_attrs) 404 row_attrs = property(_row_attrs) 333 405 334 406 def _is_hidden(self): 335 407 "Returns True if this BoundField's widget is hidden." 336 return self. field.widget.is_hidden408 return self.widget.is_hidden 337 409 is_hidden = property(_is_hidden) 338 410 339 411 def _auto_id(self): -
django/newforms/models.py
=== modified file 'django/newforms/models.py'
7 7 8 8 from django.utils.translation import ugettext_lazy as _ 9 9 from django.utils.encoding import smart_unicode 10 from django.utils.datastructures import SortedDict 11 from django.core.exceptions import ImproperlyConfigured 10 from django.utils.datastructures import SortedDict, InheritableOptions 12 11 13 12 from util import ValidationError, ErrorList 14 from forms import BaseForm, get_declared_fields13 from forms import FormOptions, FormMetaclass, BaseForm 15 14 from fields import Field, ChoiceField, EMPTY_VALUES 16 15 from widgets import Select, SelectMultiple, MultipleHiddenInput 17 16 … … 205 204 field_list.append((f.name, formfield)) 206 205 return SortedDict(field_list) 207 206 208 class ModelFormOptions(object): 209 def __init__(self, options=None): 210 self.model = getattr(options, 'model', None) 211 self.fields = getattr(options, 'fields', None) 212 self.exclude = getattr(options, 'exclude', None) 213 214 215 class ModelFormMetaclass(type): 216 def __new__(cls, name, bases, attrs, 217 formfield_callback=lambda f: f.formfield()): 218 try: 219 parents = [b for b in bases if issubclass(b, ModelForm)] 220 except NameError: 221 # We are defining ModelForm itself. 222 parents = None 223 if not parents: 224 return super(ModelFormMetaclass, cls).__new__(cls, name, bases, 225 attrs) 226 227 new_class = type.__new__(cls, name, bases, attrs) 228 declared_fields = get_declared_fields(bases, attrs, False) 229 opts = new_class._meta = ModelFormOptions(getattr(new_class, 'Meta', None)) 230 if opts.model: 231 # If a model is defined, extract form fields from it. 232 fields = fields_for_model(opts.model, opts.fields, 233 opts.exclude, formfield_callback) 234 # Override default model fields with any custom declared ones 235 # (plus, include all the other declared fields). 236 fields.update(declared_fields) 237 else: 238 fields = declared_fields 239 new_class.declared_fields = declared_fields 240 new_class.base_fields = fields 241 return new_class 207 class ModelFormOptions(InheritableOptions): 208 _model_options = { 209 'model': None, 210 'formfield_for_dbfield': lambda self, dbfield: dbfield.formfield(), 211 } 212 _default_options = FormOptions._default_options.copy() 213 _default_options.update(_model_options) 214 215 class ModelFormMetaclass(FormMetaclass): 216 217 def create_model_fields(cls, new_cls): 218 fields = [] 219 if new_cls._meta.model: 220 for dbfield in new_cls._meta.model._meta.fields + new_cls._meta.model._meta.many_to_many: 221 if dbfield.editable: 222 formfield = new_cls._meta.formfield_for_dbfield(new_cls._meta, dbfield) 223 if formfield: 224 fields.append((dbfield.name, formfield)) 225 new_cls._model_fields = SortedDict(fields) 226 create_model_fields = classmethod(create_model_fields) 227 228 def create_base_fields_pool_from_model_fields_and_declared_fields(cls, new_cls): 229 model_fields = [] 230 declared_fields = [] 231 # Add all declared fields from this class and from superclasses. Note 232 # that we loop over the bases in *reverse*. This is necessary 233 # in order to preserve the correct order of fields. Also add first 234 # model fields. 235 for base in new_cls.__mro__[::-1]: 236 try: 237 declared_fields += base._declared_fields.items() 238 if base._meta.model: 239 model_fields = base._model_fields.items() 240 except AttributeError: 241 pass 242 new_cls._base_fields_pool = SortedDict(model_fields + declared_fields) 243 create_base_fields_pool_from_model_fields_and_declared_fields = classmethod(create_base_fields_pool_from_model_fields_and_declared_fields) 244 245 def __new__(cls, name, bases, attrs): 246 new_cls = type.__new__(cls, name, bases, attrs) 247 cls.create_options(new_cls) 248 cls.create_model_fields(new_cls) 249 cls.create_declared_fields(new_cls) 250 cls.create_base_fields_pool_from_model_fields_and_declared_fields(new_cls) 251 cls.create_base_fields_from_base_fields_pool(new_cls) 252 return new_cls 242 253 243 254 class BaseModelForm(BaseForm): 244 255 def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, … … 251 262 object_data = {} 252 263 else: 253 264 self.instance = instance 254 object_data = model_to_dict(instance, opts.fields, opts.exclude)265 object_data = model_to_dict(instance, self.base_fields.keys()) 255 266 # if initial was provided, it should override the values from instance 256 267 if initial is not None: 257 268 object_data.update(initial) … … 269 280 fail_message = 'created' 270 281 else: 271 282 fail_message = 'changed' 272 return save_instance(self, self.instance, self. _meta.fields, fail_message, commit)283 return save_instance(self, self.instance, self.base_fields.keys(), fail_message, commit) 273 284 274 285 class ModelForm(BaseModelForm): 275 286 __metaclass__ = ModelFormMetaclass 287 options = ModelFormOptions 276 288 277 289 278 290 # Fields ##################################################################### -
django/newforms/util.py
=== modified file 'django/newforms/util.py'
1 from django.utils.html import escape1 from django.utils.html import conditional_escape 2 2 from django.utils.encoding import smart_unicode, StrAndUnicode, force_unicode 3 from django.utils.functional import Promise4 3 from django.utils.safestring import mark_safe 5 4 6 5 def flatatt(attrs): … … 10 9 XML-style pairs. It is assumed that the keys do not need to be XML-escaped. 11 10 If the passed dictionary is empty, then return an empty string. 12 11 """ 13 return u''.join([u' %s="%s"' % (k, escape(v)) for k, v in attrs.items()])12 return mark_safe(u''.join([u' %s="%s"' % (k, conditional_escape(v)) for k, v in attrs.items()])) 14 13 15 14 class ErrorDict(dict, StrAndUnicode): 16 15 """ … … 24 23 def as_ul(self): 25 24 if not self: return u'' 26 25 return mark_safe(u'<ul class="errorlist">%s</ul>' 27 % ''.join([u'<li>%s%s</li>' % (k, force_unicode(v))26 % ''.join([u'<li>%s%s</li>' % (k, conditional_escape(force_unicode(v))) 28 27 for k, v in self.items()])) 29 28 30 29 def as_text(self): … … 40 39 def as_ul(self): 41 40 if not self: return u'' 42 41 return mark_safe(u'<ul class="errorlist">%s</ul>' 43 % ''.join([u'<li>%s</li>' % force_unicode(e) for e in self]))42 % ''.join([u'<li>%s</li>' % conditional_escape(force_unicode(e)) for e in self])) 44 43 45 44 def as_text(self): 46 45 if not self: return u'' -
django/newforms/widgets.py
=== modified file 'django/newforms/widgets.py'
29 29 is_hidden = False # Determines whether this corresponds to an <input type="hidden">. 30 30 needs_multipart_form = False # Determines does this widget need multipart-encrypted form 31 31 32 def __init__(self, attrs=None ):32 def __init__(self, attrs=None, row_attrs=None): 33 33 if attrs is not None: 34 34 self.attrs = attrs.copy() 35 35 else: 36 36 self.attrs = {} 37 if row_attrs is not None: 38 self.row_attrs = row_attrs.copy() 39 else: 40 self.row_attrs = {} 37 41 38 42 def __deepcopy__(self, memo): 39 43 obj = copy.copy(self) 40 44 obj.attrs = self.attrs.copy() 45 obj.row_attrs = self.row_attrs.copy() 41 46 memo[id(self)] = obj 42 47 return obj 43 48 … … 77 82 return id_ 78 83 id_for_label = classmethod(id_for_label) 79 84 85 def label_tag(self, id_, label): 86 "Returns label rendered as HTML <label>." 87 return u'<label for="%s">%s</label>' % (id_, label) 88 89 def for_table(self, rendered_widget, rendered_errors, label_tag, help_text, row_attrs): 90 "Returns this widget rendered as HTML <tr>." 91 if help_text: 92 help_text = u'<br />%s' % help_text 93 return u'<tr%(row_attrs)s><th>%(label_tag)s</th><td>%(rendered_errors)s%(rendered_widget)s%(help_text)s</td></tr>' % locals() 94 95 def for_ul(self, rendered_widget, rendered_errors, label_tag, help_text, row_attrs): 96 "Returns this widget rendered as HTML <li>." 97 if help_text: 98 help_text = u' %s' % help_text 99 return u'<li%(row_attrs)s>%(rendered_errors)s%(label_tag)s %(rendered_widget)s%(help_text)s</li>' % locals() 100 101 def for_p(self, rendered_widget, rendered_errors, label_tag, help_text, row_attrs): 102 "Returns this widget rendered as HTML <p>." 103 if help_text: 104 help_text = u' %s' % help_text 105 return u'%(rendered_errors)s<p%(row_attrs)s>%(label_tag)s %(rendered_widget)s%(help_text)s</p>' % locals() 106 80 107 class Input(Widget): 81 108 """ 82 109 Base class for all <input> widgets (except type='checkbox' and … … 98 125 class PasswordInput(Input): 99 126 input_type = 'password' 100 127 101 def __init__(self, attrs=None, r ender_value=True):102 super(PasswordInput, self).__init__(attrs )128 def __init__(self, attrs=None, row_attrs=None, render_value=True): 129 super(PasswordInput, self).__init__(attrs, row_attrs) 103 130 self.render_value = render_value 104 131 105 132 def render(self, name, value, attrs=None): … … 115 142 A widget that handles <input type="hidden"> for fields that have a list 116 143 of values. 117 144 """ 118 def __init__(self, attrs=None, choices=()):119 super(MultipleHiddenInput, self).__init__(attrs )145 def __init__(self, attrs=None, row_attrs=None, choices=()): 146 super(MultipleHiddenInput, self).__init__(attrs, row_attrs) 120 147 # choices can be any iterable 121 148 self.choices = choices 122 149 … … 144 171 return files.get(name, None) 145 172 146 173 class Textarea(Widget): 147 def __init__(self, attrs=None ):174 def __init__(self, attrs=None, row_attrs=None): 148 175 # The 'rows' and 'cols' attributes are required for HTML correctness. 149 self.attrs = {'cols': '40', 'rows': '10'}176 default_attrs = {'cols': '40', 'rows': '10'} 150 177 if attrs: 151 self.attrs.update(attrs) 178 default_attrs.update(attrs) 179 super(Textarea, self).__init__(default_attrs, row_attrs) 152 180 153 181 def render(self, name, value, attrs=None): 154 182 if value is None: value = '' … … 161 189 input_type = 'text' 162 190 format = '%Y-%m-%d %H:%M:%S' # '2006-10-25 14:30:59' 163 191 164 def __init__(self, attrs=None, format=None):165 super(DateTimeInput, self).__init__(attrs )192 def __init__(self, attrs=None, row_attrs=None, format=None): 193 super(DateTimeInput, self).__init__(attrs, row_attrs) 166 194 if format: 167 195 self.format = format 168 196 … … 174 202 return super(DateTimeInput, self).render(name, value, attrs) 175 203 176 204 class CheckboxInput(Widget): 177 def __init__(self, attrs=None, check_test=bool):178 super(CheckboxInput, self).__init__(attrs )205 def __init__(self, attrs=None, row_attrs=None, check_test=bool): 206 super(CheckboxInput, self).__init__(attrs, row_attrs) 179 207 # check_test is a callable that takes a value and returns True 180 208 # if the checkbox should be checked for that value. 181 209 self.check_test = check_test … … 201 229 return super(CheckboxInput, self).value_from_datadict(data, files, name) 202 230 203 231 class Select(Widget): 204 def __init__(self, attrs=None, choices=()):205 super(Select, self).__init__(attrs )232 def __init__(self, attrs=None, row_attrs=None, choices=()): 233 super(Select, self).__init__(attrs, row_attrs) 206 234 # choices can be any iterable, but we may need to render this widget 207 235 # multiple times. Thus, collapse it into a list so it can be consumed 208 236 # more than once. … … 227 255 """ 228 256 A Select Widget intended to be used with NullBooleanField. 229 257 """ 230 def __init__(self, attrs=None ):258 def __init__(self, attrs=None, row_attrs=None): 231 259 choices = ((u'1', ugettext('Unknown')), (u'2', ugettext('Yes')), (u'3', ugettext('No'))) 232 super(NullBooleanSelect, self).__init__(attrs, choices)260 super(NullBooleanSelect, self).__init__(attrs, row_attrs, choices) 233 261 234 262 def render(self, name, value, attrs=None, choices=()): 235 263 try: … … 242 270 value = data.get(name, None) 243 271 return {u'2': True, u'3': False, True: True, False: False}.get(value, None) 244 272 245 class SelectMultiple(Widget): 246 def __init__(self, attrs=None, choices=()): 247 super(SelectMultiple, self).__init__(attrs) 248 # choices can be any iterable 249 self.choices = choices 250 273 class SelectMultiple(Select): 251 274 def render(self, name, value, attrs=None, choices=()): 252 275 if value is None: value = [] 253 276 final_attrs = self.build_attrs(attrs, name=name) … … 406 429 407 430 You'll probably want to use this class with MultiValueField. 408 431 """ 409 def __init__(self, widgets, attrs=None ):432 def __init__(self, widgets, attrs=None, row_attrs=None): 410 433 self.widgets = [isinstance(w, type) and w() or w for w in widgets] 411 super(MultiWidget, self).__init__(attrs )434 super(MultiWidget, self).__init__(attrs, row_attrs) 412 435 413 436 def render(self, name, value, attrs=None): 414 437 # value is a list of values, each corresponding to a widget … … 460 483 """ 461 484 A Widget that splits datetime input into two <input type="text"> boxes. 462 485 """ 463 def __init__(self, attrs=None ):486 def __init__(self, attrs=None, row_attrs=None): 464 487 widgets = (TextInput(attrs=attrs), TextInput(attrs=attrs)) 465 super(SplitDateTimeWidget, self).__init__(widgets, attrs )488 super(SplitDateTimeWidget, self).__init__(widgets, attrs, row_attrs) 466 489 467 490 def decompress(self, value): 468 491 if value: -
django/utils/datastructures.py
=== modified file 'django/utils/datastructures.py'
339 339 d = dict(self, content='<omitted>') 340 340 return dict.__repr__(d) 341 341 return dict.__repr__(self) 342 343 class InheritableOptions(object): 344 """ 345 A structure for meta attributes. Each subclass must have _defined_options 346 with names and default values. 347 """ 348 def __init__(self, cls): 349 # Start with default options. 350 self.__dict__.update(self._default_options) 351 # Add options defined in bases - loop over them in *reverse*. 352 for base in cls.__mro__[::-1]: 353 try: 354 self.__dict__.update(base._meta._defined_options) 355 except AttributeError: 356 pass 357 # Add options defined here. 358 self._defined_options = {} 359 for name in self._default_options: 360 try: 361 option = getattr(cls.Meta, name) 362 # Use function if is available. 363 try: 364 self._defined_options[name] = option.im_func 365 except AttributeError: 366 self._defined_options[name] = option 367 except AttributeError: 368 pass 369 self.__dict__.update(self._defined_options) -
tests/modeltests/model_forms/models.py
=== modified file 'tests/modeltests/model_forms/models.py'
144 144 ... exclude = ['url'] 145 145 146 146 >>> CategoryForm.base_fields.keys() 147 ['name' ]147 ['name', 'url'] 148 148 149 149 Don't allow more than one 'model' definition in the inheritance hierarchy. 150 150 Technically, it would generate a valid form, but the fact that the resulting … … 194 194 ... class Meta: 195 195 ... model = Category 196 196 >>> class SubCategoryForm(CategoryForm): 197 ... class Meta (CategoryForm.Meta):197 ... class Meta: 198 198 ... exclude = ['url'] 199 199 200 200 >>> print SubCategoryForm() -
tests/regressiontests/forms/extra.py
=== modified file 'tests/regressiontests/forms/extra.py'
372 372 >>> f = CommentForm(data, auto_id=False, error_class=DivErrorList) 373 373 >>> print f.as_p() 374 374 <p>Name: <input type="text" name="name" maxlength="50" /></p> 375 <div class="errorlist"><div class="error">Enter a valid e-mail address.</div></div> 376 <p>Email: <input type="text" name="email" value="invalid" /></p> 377 <div class="errorlist"><div class="error">This field is required.</div></div> 378 <p>Comment: <input type="text" name="comment" /></p> 375 <div class="errorlist"><div class="error">Enter a valid e-mail address.</div></div><p>Email: <input type="text" name="email" value="invalid" /></p> 376 <div class="errorlist"><div class="error">This field is required.</div></div><p>Comment: <input type="text" name="comment" /></p> 379 377 380 378 ################################# 381 379 # Test multipart-encoded form # -
tests/regressiontests/forms/forms.py
=== modified file 'tests/regressiontests/forms/forms.py'
89 89 <li><ul class="errorlist"><li>This field is required.</li></ul><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></li> 90 90 <li><ul class="errorlist"><li>This field is required.</li></ul><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></li> 91 91 >>> print p.as_p() 92 <ul class="errorlist"><li>This field is required.</li></ul> 93 <p><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></p> 94 <ul class="errorlist"><li>This field is required.</li></ul> 95 <p><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></p> 96 <ul class="errorlist"><li>This field is required.</li></ul> 97 <p><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></p> 92 <ul class="errorlist"><li>This field is required.</li></ul><p><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></p> 93 <ul class="errorlist"><li>This field is required.</li></ul><p><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></p> 94 <ul class="errorlist"><li>This field is required.</li></ul><p><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></p> 98 95 99 96 If you don't pass any values to the Form's __init__(), or if you pass None, 100 97 the Form will be considered unbound and won't do any validation. Form.errors … … 543 540 ... composers = MultipleChoiceField(choices=[('J', 'John Lennon'), ('P', 'Paul McCartney')], widget=MultipleHiddenInput) 544 541 >>> f = SongFormHidden(MultiValueDict(dict(name=['Yesterday'], composers=['J', 'P'])), auto_id=False) 545 542 >>> print f.as_ul() 546 <li>Name: <input type="text" name="name" value="Yesterday" /><input type="hidden" name="composers" value="J" /> 543 <li>Name: <input type="text" name="name" value="Yesterday" /></li> 544 <li><input type="hidden" name="composers" value="J" /> 547 545 <input type="hidden" name="composers" value="P" /></li> 548 546 549 547 When using CheckboxSelectMultiple, the framework expects a list of input and … … 768 766 >>> print p 769 767 <tr><th>First name:</th><td><input type="text" name="first_name" /></td></tr> 770 768 <tr><th>Last name:</th><td><input type="text" name="last_name" /></td></tr> 771 <tr><th>Birthday:</th><td><input type="text" name="birthday" /><input type="hidden" name="hidden_text" /></td></tr> 769 <tr><th>Birthday:</th><td><input type="text" name="birthday" /></td></tr> 770 <tr><td colspan="2"><input type="hidden" name="hidden_text" /></td></tr> 772 771 >>> print p.as_ul() 773 772 <li>First name: <input type="text" name="first_name" /></li> 774 773 <li>Last name: <input type="text" name="last_name" /></li> 775 <li>Birthday: <input type="text" name="birthday" /><input type="hidden" name="hidden_text" /></li> 774 <li>Birthday: <input type="text" name="birthday" /></li> 775 <li><input type="hidden" name="hidden_text" /></li> 776 776 >>> print p.as_p() 777 777 <p>First name: <input type="text" name="first_name" /></p> 778 778 <p>Last name: <input type="text" name="last_name" /></p> 779 <p>Birthday: <input type="text" name="birthday" /><input type="hidden" name="hidden_text" /></p> 779 <p>Birthday: <input type="text" name="birthday" /></p> 780 <p><input type="hidden" name="hidden_text" /></p> 780 781 781 782 With auto_id set, a HiddenInput still gets an ID, but it doesn't get a label. 782 783 >>> p = Person(auto_id='id_%s') 783 784 >>> print p 784 785 <tr><th><label for="id_first_name">First name:</label></th><td><input type="text" name="first_name" id="id_first_name" /></td></tr> 785 786 <tr><th><label for="id_last_name">Last name:</label></th><td><input type="text" name="last_name" id="id_last_name" /></td></tr> 786 <tr><th><label for="id_birthday">Birthday:</label></th><td><input type="text" name="birthday" id="id_birthday" /><input type="hidden" name="hidden_text" id="id_hidden_text" /></td></tr> 787 <tr><th><label for="id_birthday">Birthday:</label></th><td><input type="text" name="birthday" id="id_birthday" /></td></tr> 788 <tr><td colspan="2"><input type="hidden" name="hidden_text" id="id_hidden_text" /></td></tr> 787 789 >>> print p.as_ul() 788 790 <li><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></li> 789 791 <li><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></li> 790 <li><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /><input type="hidden" name="hidden_text" id="id_hidden_text" /></li> 792 <li><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></li> 793 <li><input type="hidden" name="hidden_text" id="id_hidden_text" /></li> 791 794 >>> print p.as_p() 792 795 <p><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></p> 793 796 <p><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></p> 794 <p><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /><input type="hidden" name="hidden_text" id="id_hidden_text" /></p> 797 <p><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></p> 798 <p><input type="hidden" name="hidden_text" id="id_hidden_text" /></p> 795 799 796 800 If a field with a HiddenInput has errors, the as_table() and as_ul() output 797 801 will include the error message(s) with the text "(Hidden field [fieldname]) " … … 802 806 <tr><td colspan="2"><ul class="errorlist"><li>(Hidden field hidden_text) This field is required.</li></ul></td></tr> 803 807 <tr><th>First name:</th><td><input type="text" name="first_name" value="John" /></td></tr> 804 808 <tr><th>Last name:</th><td><input type="text" name="last_name" value="Lennon" /></td></tr> 805 <tr><th>Birthday:</th><td><input type="text" name="birthday" value="1940-10-9" /><input type="hidden" name="hidden_text" /></td></tr> 809 <tr><th>Birthday:</th><td><input type="text" name="birthday" value="1940-10-9" /></td></tr> 810 <tr><td colspan="2"><input type="hidden" name="hidden_text" /></td></tr> 806 811 >>> print p.as_ul() 807 812 <li><ul class="errorlist"><li>(Hidden field hidden_text) This field is required.</li></ul></li> 808 813 <li>First name: <input type="text" name="first_name" value="John" /></li> 809 814 <li>Last name: <input type="text" name="last_name" value="Lennon" /></li> 810 <li>Birthday: <input type="text" name="birthday" value="1940-10-9" /><input type="hidden" name="hidden_text" /></li> 815 <li>Birthday: <input type="text" name="birthday" value="1940-10-9" /></li> 816 <li><input type="hidden" name="hidden_text" /></li> 811 817 >>> print p.as_p() 812 818 <ul class="errorlist"><li>(Hidden field hidden_text) This field is required.</li></ul> 813 819 <p>First name: <input type="text" name="first_name" value="John" /></p> 814 820 <p>Last name: <input type="text" name="last_name" value="Lennon" /></p> 815 <p>Birthday: <input type="text" name="birthday" value="1940-10-9" /><input type="hidden" name="hidden_text" /></p> 821 <p>Birthday: <input type="text" name="birthday" value="1940-10-9" /></p> 822 <p><input type="hidden" name="hidden_text" /></p> 816 823 817 824 A corner case: It's possible for a form to have only HiddenInputs. 818 825 >>> class TestForm(Form): … … 820 827 ... bar = CharField(widget=HiddenInput) 821 828 >>> p = TestForm(auto_id=False) 822 829 >>> print p.as_table() 823 < input type="hidden" name="foo" /><input type="hidden" name="bar" />830 <tr><td colspan="2"><input type="hidden" name="foo" /><input type="hidden" name="bar" /></td></tr> 824 831 >>> print p.as_ul() 825 < input type="hidden" name="foo" /><input type="hidden" name="bar" />832 <li><input type="hidden" name="foo" /><input type="hidden" name="bar" /></li> 826 833 >>> print p.as_p() 827 < input type="hidden" name="foo" /><input type="hidden" name="bar" />834 <p><input type="hidden" name="foo" /><input type="hidden" name="bar" /></p> 828 835 829 836 A Form's fields are displayed in the same order in which they were defined. 830 837 >>> class TestForm(Form): … … 1175 1182 >>> p = UserRegistration(auto_id=False) 1176 1183 >>> print p.as_ul() 1177 1184 <li>Username: <input type="text" name="username" maxlength="10" /> e.g., user@example.com</li> 1178 <li>Password: <input type="password" name="password" /><input type="hidden" name="next" value="/" /></li> 1185 <li>Password: <input type="password" name="password" /></li> 1186 <li><input type="hidden" name="next" value="/" /></li> 1179 1187 1180 1188 Help text can include arbitrary Unicode characters. 1181 1189 >>> class UserRegistration(Form): … … 1219 1227 ... haircut_type = CharField() 1220 1228 >>> b = Beatle(auto_id=False) 1221 1229 >>> print b.as_ul() 1230 <li>Instrument: <input type="text" name="instrument" /></li> 1222 1231 <li>First name: <input type="text" name="first_name" /></li> 1223 1232 <li>Last name: <input type="text" name="last_name" /></li> 1224 1233 <li>Birthday: <input type="text" name="birthday" /></li> 1225 <li>Instrument: <input type="text" name="instrument" /></li>1226 1234 <li>Haircut type: <input type="text" name="haircut_type" /></li> 1227 1235 1228 1236 # Forms with prefixes ######################################################### … … 1524 1532 Recall from above that passing the "auto_id" argument to a Form gives each 1525 1533 field an "id" attribute. 1526 1534 >>> t = Template('''<form action=""> 1527 ... <p>{{ form.username.label_tag }} :{{ form.username }}</p>1528 ... <p>{{ form.password1.label_tag }} :{{ form.password1 }}</p>1529 ... <p>{{ form.password2.label_tag }} :{{ form.password2 }}</p>1535 ... <p>{{ form.username.label_tag }} {{ form.username }}</p> 1536 ... <p>{{ form.password1.label_tag }} {{ form.password1 }}</p> 1537 ... <p>{{ form.password2.label_tag }} {{ form.password2 }}</p> 1530 1538 ... <input type="submit" /> 1531 1539 ... </form>''') 1532 1540 >>> print t.render(Context({'form': UserRegistration(auto_id=False)})) … … 1538 1546 </form> 1539 1547 >>> print t.render(Context({'form': UserRegistration(auto_id='id_%s')})) 1540 1548 <form action=""> 1541 <p><label for="id_username">Username </label>:<input id="id_username" type="text" name="username" maxlength="10" /></p>1542 <p><label for="id_password1">Password1 </label>:<input type="password" name="password1" id="id_password1" /></p>1543 <p><label for="id_password2">Password2 </label>:<input type="password" name="password2" id="id_password2" /></p>1549 <p><label for="id_username">Username:</label> <input id="id_username" type="text" name="username" maxlength="10" /></p> 1550 <p><label for="id_password1">Password1:</label> <input type="password" name="password1" id="id_password1" /></p> 1551 <p><label for="id_password2">Password2:</label> <input type="password" name="password2" id="id_password2" /></p> 1544 1552 <input type="submit" /> 1545 1553 </form> 1546 1554 1547 1555 User form.[field].help_text to output a field's help text. If the given field 1548 1556 does not have help text, nothing will be output. 1549 1557 >>> t = Template('''<form action=""> 1550 ... <p>{{ form.username.label_tag }} :{{ form.username }}<br />{{ form.username.help_text }}</p>1551 ... <p>{{ form.password1.label_tag }} :{{ form.password1 }}</p>1552 ... <p>{{ form.password2.label_tag }} :{{ form.password2 }}</p>1558 ... <p>{{ form.username.label_tag }} {{ form.username }}<br />{{ form.username.help_text }}</p> 1559 ... <p>{{ form.password1.label_tag }} {{ form.password1 }}</p> 1560 ... <p>{{ form.password2.label_tag }} {{ form.password2 }}</p> 1553 1561 ... <input type="submit" /> 1554 1562 ... </form>''') 1555 1563 >>> print t.render(Context({'form': UserRegistration(auto_id=False)})) … … 1562 1570 >>> Template('{{ form.password1.help_text }}').render(Context({'form': UserRegistration(auto_id=False)})) 1563 1571 u'' 1564 1572 1565 The label_tag() method takes an optional attrs argument: a dictionary of HTML1566 attributes to add to the <label> tag.1567 >>> f = UserRegistration(auto_id='id_%s')1568 >>> for bf in f:1569 ... print bf.label_tag(attrs={'class': 'pretty'})1570 <label for="id_username" class="pretty">Username</label>1571 <label for="id_password1" class="pretty">Password1</label>1572 <label for="id_password2" class="pretty">Password2</label>1573 1574 1573 To display the errors that aren't associated with a particular field -- e.g., 1575 1574 the errors caused by Form.clean() -- use {{ form.non_field_errors }} in the 1576 1575 template. If used on its own, it is displayed as a <ul> (or an empty string, if -
tests/regressiontests/forms/regressions.py
=== modified file 'tests/regressiontests/forms/regressions.py'
56 56 >>> activate('ru') 57 57 >>> f = SomeForm({}) 58 58 >>> f.as_p() 59 u'<ul class="errorlist"><li>\u041e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u043f\u043e\u043b\u0435.</li></ul> \n<p><label for="id_somechoice_0">\xc5\xf8\xdf:</label> <ul>\n<li><label><input type="radio" id="id_somechoice_0" value="\xc5" name="somechoice" /> En tied\xe4</label></li>\n<li><label><input type="radio" id="id_somechoice_1" value="\xf8" name="somechoice" /> Mies</label></li>\n<li><label><input type="radio" id="id_somechoice_2" value="\xdf" name="somechoice" /> Nainen</label></li>\n</ul></p>'59 u'<ul class="errorlist"><li>\u041e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u043f\u043e\u043b\u0435.</li></ul><p><label for="id_somechoice_0">\xc5\xf8\xdf:</label> <ul>\n<li><label><input type="radio" id="id_somechoice_0" value="\xc5" name="somechoice" /> En tied\xe4</label></li>\n<li><label><input type="radio" id="id_somechoice_1" value="\xf8" name="somechoice" /> Mies</label></li>\n<li><label><input type="radio" id="id_somechoice_2" value="\xdf" name="somechoice" /> Nainen</label></li>\n</ul></p>' 60 60 >>> deactivate() 61 61 62 62 Deep copying translated text shouldn't raise an error