Ticket #6630: 00-newforms-fieldsets.3.diff
File 00-newforms-fieldsets.3.diff, 45.3 KB (added by , 17 years ago) |
---|
-
django/newforms/metaclassing.py
=== added file 'django/newforms/metaclassing.py'
1 from django.core.exceptions import ImproperlyConfigured 2 from django.utils.datastructures import SortedDict 3 4 from fields import Field 5 6 def create_meta(cls): 7 cls._meta = cls._options(getattr(cls, 'Meta', None)) 8 9 def create_declared_fields(cls): 10 fields = [] 11 for name, attr in cls.__dict__.items(): 12 if isinstance(attr, Field): 13 fields.append((name, attr)) 14 delattr(cls, name) 15 fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter)) 16 cls._declared_fields = SortedDict(fields) 17 18 def create_model_fields(cls, formfield_callback): 19 fields = [] 20 if cls._meta.model: 21 for dbfield in cls._meta.model._meta.fields + cls._meta.model._meta.many_to_many: 22 if dbfield.editable: 23 formfield = formfield_callback(dbfield) 24 if formfield: 25 fields.append((dbfield.name, formfield)) 26 cls._model_fields = SortedDict(fields) 27 28 def create_base_fields_pool_from_declared_fields(cls): 29 fields = [] 30 for base in cls.__mro__[::-1]: 31 try: 32 fields += base._declared_fields.items() 33 except AttributeError: 34 pass 35 cls._base_fields_pool = SortedDict(fields) 36 37 def create_base_fields_pool_from_model_fields_and_declared_fields(cls): 38 model_fields, declared_fields = [], [] 39 for base in cls.__mro__[::-1]: 40 try: 41 declared_fields += base._declared_fields.items() 42 if base._meta.model: 43 model_fields = base._model_fields.items() 44 except AttributeError: 45 pass 46 cls._base_fields_pool = SortedDict(model_fields + declared_fields) 47 48 def create_base_fields_from_base_fields_pool(cls): 49 if (cls._meta.fieldsets is None) + (cls._meta.fields is None) + (cls._meta.exclude is None) < 2: 50 raise ImproperlyConfigured("%s cannot have more than one option from fieldsets, fields and exclude." % cls.__name__) 51 if cls._meta.fieldsets: 52 names = [] 53 for fieldset in cls._meta.fieldsets: 54 names.extend(fieldset['fields']) 55 elif cls._meta.fields: 56 names = cls._meta.fields 57 elif cls._meta.exclude: 58 names = [name for name in cls._base_fields_pool if name not in cls._meta.exclude] 59 else: 60 names = cls._base_fields_pool.keys() 61 cls.base_fields = SortedDict([(name, cls._base_fields_pool[name]) for name in names]) -
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 escape 7 from django.utils.html import conditional_escape 9 8 from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode 10 9 from django.utils.safestring import mark_safe 11 10 12 from fields import Fi eld, FileField11 from fields import FileField 13 12 from widgets import TextInput, Textarea 14 13 from util import flatatt, ErrorDict, ErrorList, ValidationError 14 import metaclassing 15 15 16 16 __all__ = ('BaseForm', 'Form') 17 17 … … 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(object): 26 def __init__(self, options=None): 27 self.fieldsets = getattr(options, 'fieldsets', None) 28 self.fields = getattr(options, 'fields', None) 29 self.exclude = getattr(options, 'exclude', None) 30 31 class FormMetaclass(type): 57 32 def __new__(cls, name, bases, attrs): 58 attrs['base_fields'] = get_declared_fields(bases, attrs) 59 return type.__new__(cls, name, bases, attrs) 33 new_class = type.__new__(cls, name, bases, attrs) 34 metaclassing.create_meta(new_class) 35 metaclassing.create_declared_fields(new_class) 36 metaclassing.create_base_fields_pool_from_declared_fields(new_class) 37 metaclassing.create_base_fields_from_base_fields_pool(new_class) 38 return new_class 60 39 61 40 class BaseForm(StrAndUnicode): 62 41 # This is the main implementation of all the Form logic. Note that this … … 98 77 return BoundField(self, field, name) 99 78 100 79 def _get_errors(self): 101 "Returns an ErrorDict for the data provided for the form "80 "Returns an ErrorDict for the data provided for the form." 102 81 if self._errors is None: 103 82 self.full_clean() 104 83 return self._errors … … 111 90 """ 112 91 return self.is_bound and not bool(self.errors) 113 92 114 def add_prefix(self, field_name): 93 def has_fieldsets(self): 94 "Returns True if this form has fieldsets." 95 return bool(self._meta.fieldsets) 96 97 def add_prefix(self, name): 115 98 """ 116 99 Returns the field name with a prefix appended, if this Form has a 117 100 prefix set. 118 101 119 102 Subclasses may wish to override. 120 103 """ 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): 104 return self.prefix and ('%s-%s' % (self.prefix, name)) or name 105 106 def first_fieldset_attributes(self): 107 "Returns attributes for first fieldset as HTML code." 108 if self.has_fieldsets() and 'attrs' in self._meta.fieldsets[0]: 109 return flatatt(self._meta.fieldsets[0]['attrs']) 110 else: 111 return u'' 112 113 def first_fieldset_legend_tag(self): 114 "Returns legend tag for first fieldset as HTML code." 115 if self.has_fieldsets() and 'legend' in self._meta.fieldsets[0]: 116 return mark_safe(u'<legend>%s</legend>' % conditional_escape(force_unicode(self._meta.fieldsets[0]['legend']))) 117 else: 118 return u'' 119 120 def _label_tag_html_output(self, bf, label_tag_html): 121 "Helper function for outputting HTML from a label. Used by _widget_html_output." 122 label, label_id = bf.label, bf.label_id 123 if self.label_suffix and label and label[-1] not in ':?.!': 124 label += self.label_suffix 125 if label and label_id: 126 return label_tag_html % { 127 'label': label, 128 'id': label_id, 129 } 130 else: 131 return label 132 133 def _help_text_html_output(self, bf, help_text_html): 134 "Helper function for outputting HTML from a help text. Used by _widget_html_output." 135 if bf.help_text: 136 return help_text_html % { 137 'help_text': bf.help_text, 138 } 139 else: 140 return u'' 141 142 def _row_html_output(self, bf, row_html, label_tag_html, help_text_html): 143 "Helper function for outputting HTML from a widget. Used by _html_output." 144 return row_html % { 145 'rendered_widget': unicode(bf), 146 'rendered_errors': unicode(bf.errors), 147 'label_tag': self._label_tag_html_output(bf, label_tag_html), 148 'help_text': self._help_text_html_output(bf, help_text_html), 149 'attrs': bf.row_attrs, 150 } 151 152 def _top_errors_html_output(self, top_errors, top_errors_html): 153 "Helper function for outputting HTML from a top errors. Used by _html_output." 154 return top_errors_html % { 155 'top_errors': unicode(top_errors), 156 } 157 158 def _fieldset_html_output(self, fields, fieldset, is_first, is_last, fieldset_start_html, fieldset_end_html, legend_tag_html): 159 "Helper function for outputting HTML from a fieldset. Used by _html_output." 160 output = [] 161 if not is_first: 162 legend_tag = attrs = u'' 163 if 'legend' in fieldset: 164 legend_tag = legend_tag_html % { 165 'legend': conditional_escape(force_unicode(fieldset['legend'])), 166 } 167 if 'attrs' in fieldset: 168 attrs = flatatt(fieldset.get('attrs')) 169 output.append(fieldset_start_html % { 170 'legend_tag': legend_tag, 171 'attrs': attrs, 172 }) 173 for name in fieldset['fields']: 174 output.append(fields[name]) 175 if not is_last: 176 output.append(fieldset_end_html) 177 return u'\n'.join(output) 178 179 def _hidden_fields_html_output(self, hidden_fields, hidden_fields_html): 180 "Helper function for outputting HTML from a hidden fields. Used by _html_output." 181 return hidden_fields_html % { 182 'hidden_fields': u''.join(hidden_fields), 183 } 184 185 def _html_output(self, row_html, label_tag_html, help_text_html, top_errors_html, 186 fieldset_start_html, legend_tag_html, fieldset_end_html, hidden_fields_html): 124 187 "Helper function for outputting HTML. Used by as_table(), as_ul(), as_p()." 125 top_errors = self.non_field_errors() # Errors that should be displayed above all fields.126 output, hidden_fields = [], []188 output = [] 189 top_errors, hidden_fields, visible_fields = self.non_field_errors(), [], {} 127 190 for name, field in self.fields.items(): 128 191 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 192 if bf.is_hidden: 131 if bf _errors:132 top_errors.extend([u'(Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_errors])193 if bf.errors: 194 top_errors.extend([u'(Hidden field %s) %s' % (name, conditional_escape(force_unicode(e))) for e in bf.errors]) 133 195 hidden_fields.append(unicode(bf)) 134 196 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}) 197 visible_fields[name] = self._row_html_output(bf, row_html, label_tag_html, help_text_html) 152 198 if top_errors: 153 output.insert(0, error_row % force_unicode(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) 199 output.append(self._top_errors_html_output(top_errors, top_errors_html)) 200 if self.has_fieldsets(): 201 for i, fieldset in enumerate(self._meta.fieldsets): 202 fields = dict((name, visible_fields[name]) for name in fieldset['fields'] if name in visible_fields) 203 is_first = (i == 0) 204 is_last = (i + 1 == len(self._meta.fieldsets)) 205 output.append(self._fieldset_html_output(fields, fieldset, is_first, is_last, 206 fieldset_start_html, fieldset_end_html, legend_tag_html)) 207 else: 208 for name in self.fields: 209 if name in visible_fields: 210 output.append(visible_fields[name]) 211 if hidden_fields: 212 output.append(self._hidden_fields_html_output(hidden_fields, hidden_fields_html)) 165 213 return mark_safe(u'\n'.join(output)) 166 214 167 215 def as_table(self): 168 216 "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) 217 kwargs = { 218 'row_html': u'<tr%(attrs)s><th>%(label_tag)s</th><td>%(rendered_errors)s%(rendered_widget)s%(help_text)s</td></tr>', 219 'label_tag_html': u'<label for="%(id)s">%(label)s</label>', 220 'help_text_html': u'<br />%(help_text)s', 221 'top_errors_html': u'<tr><td colspan="2">%(top_errors)s</td></tr>', 222 'fieldset_start_html': u'<fieldset%(attrs)s>\n%(legend_tag)s<table>', 223 'fieldset_end_html': u'</table>\n</fieldset>', 224 'legend_tag_html': u'<legend>%(legend)s</legend>\n', 225 'hidden_fields_html': u'<tr><td colspan="2">%(hidden_fields)s</td></tr>', 226 } 227 return self._html_output(**kwargs) 170 228 171 229 def as_ul(self): 172 230 "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) 231 kwargs = { 232 'row_html': u'<li%(attrs)s>%(rendered_errors)s%(label_tag)s %(rendered_widget)s%(help_text)s</li>', 233 'label_tag_html': u'<label for="%(id)s">%(label)s</label>', 234 'help_text_html': u' %(help_text)s', 235 'top_errors_html': u'<li>%(top_errors)s</li>', 236 'fieldset_start_html': u'<fieldset%(attrs)s>\n%(legend_tag)s<ul>', 237 'fieldset_end_html': u'</ul>\n</fieldset>', 238 'legend_tag_html': u'<legend>%(legend)s</legend>\n', 239 'hidden_fields_html': u'<li>%(hidden_fields)s</li>', 240 } 241 return self._html_output(**kwargs) 174 242 175 243 def as_p(self): 176 244 "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) 245 kwargs = { 246 'row_html': u'%(rendered_errors)s<p%(attrs)s>%(label_tag)s %(rendered_widget)s%(help_text)s</p>', 247 'label_tag_html': u'<label for="%(id)s">%(label)s</label>', 248 'help_text_html': u' %(help_text)s', 249 'top_errors_html': u'%(top_errors)s', 250 'fieldset_start_html': u'<fieldset%(attrs)s>\n%(legend_tag)s', 251 'fieldset_end_html': u'</fieldset>', 252 'legend_tag_html': u'<legend>%(legend)s</legend>\n', 253 'hidden_fields_html': u'<p>%(hidden_fields)s</p>', 254 } 255 return self._html_output(**kwargs) 178 256 179 257 def non_field_errors(self): 180 258 """ … … 209 287 value = getattr(self, 'clean_%s' % name)() 210 288 self.cleaned_data[name] = value 211 289 except ValidationError, e: 212 self._errors[name] = e.messages290 self._errors[name] = self.error_class(e.messages) 213 291 if name in self.cleaned_data: 214 292 del self.cleaned_data[name] 215 293 try: 216 294 self.cleaned_data = self.clean() 217 295 except ValidationError, e: 218 self._errors[NON_FIELD_ERRORS] = e.messages296 self._errors[NON_FIELD_ERRORS] = self.error_class(e.messages) 219 297 if self._errors: 220 298 delattr(self, 'cleaned_data') 221 299 … … 245 323 # fancy metaclass stuff purely for the semantic sugar -- it allows one 246 324 # to define a form using declarative syntax. 247 325 # BaseForm itself has no way of designating self.fields. 248 __metaclass__ = DeclarativeFieldsMetaclass 326 __metaclass__ = FormMetaclass 327 _options = FormOptions 249 328 250 329 class BoundField(StrAndUnicode): 251 330 "A Field plus data" 252 331 def __init__(self, form, field, name): 253 332 self.form = form 254 333 self.field = field 334 self.widget = field.widget 255 335 self.name = name 256 336 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 337 263 338 def __unicode__(self): 264 339 """Renders this field as an HTML widget.""" … … 279 354 field's default widget will be used. 280 355 """ 281 356 if not widget: 282 widget = self. field.widget357 widget = self.widget 283 358 attrs = attrs or {} 284 359 auto_id = self.auto_id 285 360 if auto_id and 'id' not in attrs and 'id' not in widget.attrs: … … 312 387 """ 313 388 Returns the data for this BoundField, or None if it wasn't given. 314 389 """ 315 return self. field.widget.value_from_datadict(self.form.data, self.form.files, self.html_name)390 return self.widget.value_from_datadict(self.form.data, self.form.files, self.html_name) 316 391 data = property(_data) 317 392 393 def _label(self): 394 "Returns label for this field as safe HTML." 395 if self.field.label is None: 396 return pretty_name(self.name) 397 else: 398 return conditional_escape(force_unicode(self.field.label)) 399 label = property(_label) 400 401 def _label_id(self): 402 "Returns label id for this field as safe HTML." 403 id_ = self.widget.attrs.get('id') or self.auto_id 404 if id_: 405 return self.widget.id_for_label(id_) 406 label_id = property(_label_id) 407 408 def _help_text(self): 409 "Returns help text for this field as safe HTML." 410 if self.field.help_text is None: 411 return u'' 412 else: 413 return force_unicode(self.field.help_text) 414 help_text = property(_help_text) 415 416 def _row_attrs(self): 417 "Returns row attributes for this field as safe HTML." 418 return flatatt(self.widget.row_attrs) 419 row_attrs = property(_row_attrs) 420 318 421 def label_tag(self, contents=None, attrs=None): 319 422 """ 320 423 Wraps the given contents in a <label>, if the field has an ID attribute. … … 323 426 324 427 If attrs are given, they're used as HTML attributes on the <label> tag. 325 428 """ 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_: 429 contents = contents or self.label 430 if self.label_id: 330 431 attrs = attrs and flatatt(attrs) or '' 331 contents = '<label for="%s"%s>%s</label>' % ( widget.id_for_label(id_), attrs, contents)432 contents = '<label for="%s"%s>%s</label>' % (self.label_id, attrs, contents) 332 433 return mark_safe(contents) 333 434 334 435 def _is_hidden(self): 335 436 "Returns True if this BoundField's widget is hidden." 336 return self. field.widget.is_hidden437 return self.widget.is_hidden 337 438 is_hidden = property(_is_hidden) 338 439 339 440 def _auto_id(self): -
django/newforms/models.py
=== modified file 'django/newforms/models.py'
8 8 from django.utils.translation import ugettext_lazy as _ 9 9 from django.utils.encoding import smart_unicode 10 10 from django.utils.datastructures import SortedDict 11 from django.core.exceptions import ImproperlyConfigured12 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 16 import metaclassing 17 17 18 18 __all__ = ( 19 19 'ModelForm', 'BaseModelForm', 'model_to_dict', 'fields_for_model', … … 205 205 field_list.append((f.name, formfield)) 206 206 return SortedDict(field_list) 207 207 208 class ModelFormOptions( object):208 class ModelFormOptions(FormOptions): 209 209 def __init__(self, options=None): 210 super(ModelFormOptions, self).__init__(options) 210 211 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 212 213 class ModelFormMetaclass(FormMetaclass): 214 def __new__(cls, name, bases, attrs, formfield_callback=lambda f: f.formfield()): 227 215 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 216 metaclassing.create_meta(new_class) 217 metaclassing.create_model_fields(new_class, formfield_callback) 218 metaclassing.create_declared_fields(new_class) 219 metaclassing.create_base_fields_pool_from_model_fields_and_declared_fields(new_class) 220 metaclassing.create_base_fields_from_base_fields_pool(new_class) 241 221 return new_class 242 222 243 223 class BaseModelForm(BaseForm): … … 251 231 object_data = {} 252 232 else: 253 233 self.instance = instance 254 object_data = model_to_dict(instance, opts.fields, opts.exclude)234 object_data = model_to_dict(instance, self.base_fields.keys()) 255 235 # if initial was provided, it should override the values from instance 256 236 if initial is not None: 257 237 object_data.update(initial) … … 269 249 fail_message = 'created' 270 250 else: 271 251 fail_message = 'changed' 272 return save_instance(self, self.instance, self. _meta.fields, fail_message, commit)252 return save_instance(self, self.instance, self.base_fields.keys(), fail_message, commit) 273 253 274 254 class ModelForm(BaseModelForm): 275 255 __metaclass__ = ModelFormMetaclass 256 _options = ModelFormOptions 276 257 277 258 278 259 # 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 … … 98 103 class PasswordInput(Input): 99 104 input_type = 'password' 100 105 101 def __init__(self, attrs=None, r ender_value=True):102 super(PasswordInput, self).__init__(attrs )106 def __init__(self, attrs=None, row_attrs=None, render_value=True): 107 super(PasswordInput, self).__init__(attrs, row_attrs) 103 108 self.render_value = render_value 104 109 105 110 def render(self, name, value, attrs=None): … … 115 120 A widget that handles <input type="hidden"> for fields that have a list 116 121 of values. 117 122 """ 118 def __init__(self, attrs=None, choices=()):119 super(MultipleHiddenInput, self).__init__(attrs )123 def __init__(self, attrs=None, row_attrs=None, choices=()): 124 super(MultipleHiddenInput, self).__init__(attrs, row_attrs) 120 125 # choices can be any iterable 121 126 self.choices = choices 122 127 … … 144 149 return files.get(name, None) 145 150 146 151 class Textarea(Widget): 147 def __init__(self, attrs=None ):152 def __init__(self, attrs=None, row_attrs=None): 148 153 # The 'rows' and 'cols' attributes are required for HTML correctness. 149 self.attrs = {'cols': '40', 'rows': '10'}154 default_attrs = {'cols': '40', 'rows': '10'} 150 155 if attrs: 151 self.attrs.update(attrs) 156 default_attrs.update(attrs) 157 super(Textarea, self).__init__(default_attrs, row_attrs) 152 158 153 159 def render(self, name, value, attrs=None): 154 160 if value is None: value = '' … … 161 167 input_type = 'text' 162 168 format = '%Y-%m-%d %H:%M:%S' # '2006-10-25 14:30:59' 163 169 164 def __init__(self, attrs=None, format=None):165 super(DateTimeInput, self).__init__(attrs )170 def __init__(self, attrs=None, row_attrs=None, format=None): 171 super(DateTimeInput, self).__init__(attrs, row_attrs) 166 172 if format: 167 173 self.format = format 168 174 … … 174 180 return super(DateTimeInput, self).render(name, value, attrs) 175 181 176 182 class CheckboxInput(Widget): 177 def __init__(self, attrs=None, check_test=bool):178 super(CheckboxInput, self).__init__(attrs )183 def __init__(self, attrs=None, row_attrs=None, check_test=bool): 184 super(CheckboxInput, self).__init__(attrs, row_attrs) 179 185 # check_test is a callable that takes a value and returns True 180 186 # if the checkbox should be checked for that value. 181 187 self.check_test = check_test … … 201 207 return super(CheckboxInput, self).value_from_datadict(data, files, name) 202 208 203 209 class Select(Widget): 204 def __init__(self, attrs=None, choices=()):205 super(Select, self).__init__(attrs )210 def __init__(self, attrs=None, row_attrs=None, choices=()): 211 super(Select, self).__init__(attrs, row_attrs) 206 212 # choices can be any iterable, but we may need to render this widget 207 213 # multiple times. Thus, collapse it into a list so it can be consumed 208 214 # more than once. … … 227 233 """ 228 234 A Select Widget intended to be used with NullBooleanField. 229 235 """ 230 def __init__(self, attrs=None ):236 def __init__(self, attrs=None, row_attrs=None): 231 237 choices = ((u'1', ugettext('Unknown')), (u'2', ugettext('Yes')), (u'3', ugettext('No'))) 232 super(NullBooleanSelect, self).__init__(attrs, choices)238 super(NullBooleanSelect, self).__init__(attrs, row_attrs, choices) 233 239 234 240 def render(self, name, value, attrs=None, choices=()): 235 241 try: … … 242 248 value = data.get(name, None) 243 249 return {u'2': True, u'3': False, True: True, False: False}.get(value, None) 244 250 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 251 class SelectMultiple(Select): 251 252 def render(self, name, value, attrs=None, choices=()): 252 253 if value is None: value = [] 253 254 final_attrs = self.build_attrs(attrs, name=name) … … 406 407 407 408 You'll probably want to use this class with MultiValueField. 408 409 """ 409 def __init__(self, widgets, attrs=None ):410 def __init__(self, widgets, attrs=None, row_attrs=None): 410 411 self.widgets = [isinstance(w, type) and w() or w for w in widgets] 411 super(MultiWidget, self).__init__(attrs )412 super(MultiWidget, self).__init__(attrs, row_attrs) 412 413 413 414 def render(self, name, value, attrs=None): 414 415 # value is a list of values, each corresponding to a widget … … 460 461 """ 461 462 A Widget that splits datetime input into two <input type="text"> boxes. 462 463 """ 463 def __init__(self, attrs=None ):464 def __init__(self, attrs=None, row_attrs=None): 464 465 widgets = (TextInput(attrs=attrs), TextInput(attrs=attrs)) 465 super(SplitDateTimeWidget, self).__init__(widgets, attrs )466 super(SplitDateTimeWidget, self).__init__(widgets, attrs, row_attrs) 466 467 467 468 def decompress(self, value): 468 469 if value: -
tests/modeltests/model_forms/models.py
=== modified file 'tests/modeltests/model_forms/models.py'
142 142 ... model = Category 143 143 ... fields = ['name', 'url'] 144 144 ... exclude = ['url'] 145 145 Traceback (most recent call last): 146 File "/home/petr/django/local2/00-newforms-fieldsets/django/test/_doctest.py", line 1267, in __run 147 compileflags, 1) in test.globs 148 File "<doctest modeltests.model_forms.models.__test__.API_TESTS[12]>", line 1, in ? 149 class CategoryForm(ModelForm): 150 File "/home/petr/django/local2/00-newforms-fieldsets/django/newforms/models.py", line 220, in __new__ 151 metaclassing.create_base_fields_from_base_fields_pool(new_class) 152 File "/home/petr/django/local2/00-newforms-fieldsets/django/newforms/metaclassing.py", line 50, in create_base_fields_from_base_fields_pool 153 raise ImproperlyConfigured("%s cannot have more than one option from fieldsets, fields and exclude." % cls.__name__) 154 ImproperlyConfigured: CategoryForm cannot have more than one option from fieldsets, fields and exclude. 146 155 >>> CategoryForm.base_fields.keys() 147 ['name' ]156 ['name', 'slug'] 148 157 149 158 Don't allow more than one 'model' definition in the inheritance hierarchy. 150 159 Technically, it would generate a valid form, but the fact that the resulting -
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 ######################################################### -
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