| 1 |
from django.core import validators |
|---|
| 2 |
from django.core.exceptions import PermissionDenied |
|---|
| 3 |
from django.utils.html import escape |
|---|
| 4 |
from django.utils.safestring import mark_safe |
|---|
| 5 |
from django.conf import settings |
|---|
| 6 |
from django.utils.translation import ugettext, ungettext |
|---|
| 7 |
from django.utils.encoding import smart_unicode, force_unicode |
|---|
| 8 |
|
|---|
| 9 |
FORM_FIELD_ID_PREFIX = 'id_' |
|---|
| 10 |
|
|---|
| 11 |
class EmptyValue(Exception): |
|---|
| 12 |
"This is raised when empty data is provided" |
|---|
| 13 |
pass |
|---|
| 14 |
|
|---|
| 15 |
class Manipulator(object): |
|---|
| 16 |
# List of permission strings. User must have at least one to manipulate. |
|---|
| 17 |
# None means everybody has permission. |
|---|
| 18 |
required_permission = '' |
|---|
| 19 |
|
|---|
| 20 |
def __init__(self): |
|---|
| 21 |
# List of FormField objects |
|---|
| 22 |
self.fields = [] |
|---|
| 23 |
|
|---|
| 24 |
def __getitem__(self, field_name): |
|---|
| 25 |
"Looks up field by field name; raises KeyError on failure" |
|---|
| 26 |
for field in self.fields: |
|---|
| 27 |
if field.field_name == field_name: |
|---|
| 28 |
return field |
|---|
| 29 |
raise KeyError, "Field %s not found\n%s" % (field_name, repr(self.fields)) |
|---|
| 30 |
|
|---|
| 31 |
def __delitem__(self, field_name): |
|---|
| 32 |
"Deletes the field with the given field name; raises KeyError on failure" |
|---|
| 33 |
for i, field in enumerate(self.fields): |
|---|
| 34 |
if field.field_name == field_name: |
|---|
| 35 |
del self.fields[i] |
|---|
| 36 |
return |
|---|
| 37 |
raise KeyError, "Field %s not found" % field_name |
|---|
| 38 |
|
|---|
| 39 |
def check_permissions(self, user): |
|---|
| 40 |
"""Confirms user has required permissions to use this manipulator; raises |
|---|
| 41 |
PermissionDenied on failure.""" |
|---|
| 42 |
if self.required_permission is None: |
|---|
| 43 |
return |
|---|
| 44 |
if user.has_perm(self.required_permission): |
|---|
| 45 |
return |
|---|
| 46 |
raise PermissionDenied |
|---|
| 47 |
|
|---|
| 48 |
def prepare(self, new_data): |
|---|
| 49 |
""" |
|---|
| 50 |
Makes any necessary preparations to new_data, in place, before data has |
|---|
| 51 |
been validated. |
|---|
| 52 |
""" |
|---|
| 53 |
for field in self.fields: |
|---|
| 54 |
field.prepare(new_data) |
|---|
| 55 |
|
|---|
| 56 |
def get_validation_errors(self, new_data): |
|---|
| 57 |
"Returns dictionary mapping field_names to error-message lists" |
|---|
| 58 |
errors = {} |
|---|
| 59 |
self.prepare(new_data) |
|---|
| 60 |
for field in self.fields: |
|---|
| 61 |
errors.update(field.get_validation_errors(new_data)) |
|---|
| 62 |
val_name = 'validate_%s' % field.field_name |
|---|
| 63 |
if hasattr(self, val_name): |
|---|
| 64 |
val = getattr(self, val_name) |
|---|
| 65 |
try: |
|---|
| 66 |
field.run_validator(new_data, val) |
|---|
| 67 |
except (validators.ValidationError, validators.CriticalValidationError), e: |
|---|
| 68 |
errors.setdefault(field.field_name, []).extend(e.messages) |
|---|
| 69 |
|
|---|
| 70 |
# if field.is_required and not new_data.get(field.field_name, False): |
|---|
| 71 |
# errors.setdefault(field.field_name, []).append(ugettext_lazy('This field is required.')) |
|---|
| 72 |
# continue |
|---|
| 73 |
# try: |
|---|
| 74 |
# validator_list = field.validator_list |
|---|
| 75 |
# if hasattr(self, 'validate_%s' % field.field_name): |
|---|
| 76 |
# validator_list.append(getattr(self, 'validate_%s' % field.field_name)) |
|---|
| 77 |
# for validator in validator_list: |
|---|
| 78 |
# if field.is_required or new_data.get(field.field_name, False) or hasattr(validator, 'always_test'): |
|---|
| 79 |
# try: |
|---|
| 80 |
# if hasattr(field, 'requires_data_list'): |
|---|
| 81 |
# validator(new_data.getlist(field.field_name), new_data) |
|---|
| 82 |
# else: |
|---|
| 83 |
# validator(new_data.get(field.field_name, ''), new_data) |
|---|
| 84 |
# except validators.ValidationError, e: |
|---|
| 85 |
# errors.setdefault(field.field_name, []).extend(e.messages) |
|---|
| 86 |
# # If a CriticalValidationError is raised, ignore any other ValidationErrors |
|---|
| 87 |
# # for this particular field |
|---|
| 88 |
# except validators.CriticalValidationError, e: |
|---|
| 89 |
# errors.setdefault(field.field_name, []).extend(e.messages) |
|---|
| 90 |
return errors |
|---|
| 91 |
|
|---|
| 92 |
def save(self, new_data): |
|---|
| 93 |
"Saves the changes and returns the new object" |
|---|
| 94 |
# changes is a dictionary-like object keyed by field_name |
|---|
| 95 |
raise NotImplementedError |
|---|
| 96 |
|
|---|
| 97 |
def do_html2python(self, new_data): |
|---|
| 98 |
""" |
|---|
| 99 |
Convert the data from HTML data types to Python datatypes, changing the |
|---|
| 100 |
object in place. This happens after validation but before storage. This |
|---|
| 101 |
must happen after validation because html2python functions aren't |
|---|
| 102 |
expected to deal with invalid input. |
|---|
| 103 |
""" |
|---|
| 104 |
for field in self.fields: |
|---|
| 105 |
field.convert_post_data(new_data) |
|---|
| 106 |
|
|---|
| 107 |
class FormWrapper(object): |
|---|
| 108 |
""" |
|---|
| 109 |
A wrapper linking a Manipulator to the template system. |
|---|
| 110 |
This allows dictionary-style lookups of formfields. It also handles feeding |
|---|
| 111 |
prepopulated data and validation error messages to the formfield objects. |
|---|
| 112 |
""" |
|---|
| 113 |
def __init__(self, manipulator, data=None, error_dict=None, edit_inline=True): |
|---|
| 114 |
self.manipulator = manipulator |
|---|
| 115 |
if data is None: |
|---|
| 116 |
data = {} |
|---|
| 117 |
if error_dict is None: |
|---|
| 118 |
error_dict = {} |
|---|
| 119 |
self.data = data |
|---|
| 120 |
self.error_dict = error_dict |
|---|
| 121 |
self._inline_collections = None |
|---|
| 122 |
self.edit_inline = edit_inline |
|---|
| 123 |
|
|---|
| 124 |
def __repr__(self): |
|---|
| 125 |
return repr(self.__dict__) |
|---|
| 126 |
|
|---|
| 127 |
def __getitem__(self, key): |
|---|
| 128 |
for field in self.manipulator.fields: |
|---|
| 129 |
if field.field_name == key: |
|---|
| 130 |
data = field.extract_data(self.data) |
|---|
| 131 |
return FormFieldWrapper(field, data, self.error_dict.get(field.field_name, [])) |
|---|
| 132 |
if self.edit_inline: |
|---|
| 133 |
self.fill_inline_collections() |
|---|
| 134 |
for inline_collection in self._inline_collections: |
|---|
| 135 |
# The 'orig_name' comparison is for backwards compatibility |
|---|
| 136 |
# with hand-crafted forms. |
|---|
| 137 |
if inline_collection.name == key or (':' not in key and inline_collection.orig_name == key): |
|---|
| 138 |
return inline_collection |
|---|
| 139 |
raise KeyError, "Could not find Formfield or InlineObjectCollection named %r" % key |
|---|
| 140 |
|
|---|
| 141 |
def fill_inline_collections(self): |
|---|
| 142 |
if not self._inline_collections: |
|---|
| 143 |
ic = [] |
|---|
| 144 |
related_objects = self.manipulator.get_related_objects() |
|---|
| 145 |
for rel_obj in related_objects: |
|---|
| 146 |
data = rel_obj.extract_data(self.data) |
|---|
| 147 |
inline_collection = InlineObjectCollection(self.manipulator, rel_obj, data, self.error_dict) |
|---|
| 148 |
ic.append(inline_collection) |
|---|
| 149 |
self._inline_collections = ic |
|---|
| 150 |
|
|---|
| 151 |
def has_errors(self): |
|---|
| 152 |
return self.error_dict != {} |
|---|
| 153 |
|
|---|
| 154 |
def _get_fields(self): |
|---|
| 155 |
try: |
|---|
| 156 |
return self._fields |
|---|
| 157 |
except AttributeError: |
|---|
| 158 |
self._fields = [self.__getitem__(field.field_name) for field in self.manipulator.fields] |
|---|
| 159 |
return self._fields |
|---|
| 160 |
|
|---|
| 161 |
fields = property(_get_fields) |
|---|
| 162 |
|
|---|
| 163 |
class FormFieldWrapper(object): |
|---|
| 164 |
"A bridge between the template system and an individual form field. Used by FormWrapper." |
|---|
| 165 |
def __init__(self, formfield, data, error_list): |
|---|
| 166 |
self.formfield, self.data, self.error_list = formfield, data, error_list |
|---|
| 167 |
self.field_name = self.formfield.field_name # for convenience in templates |
|---|
| 168 |
|
|---|
| 169 |
def __str__(self): |
|---|
| 170 |
"Renders the field" |
|---|
| 171 |
return unicode(self).encode('utf-8') |
|---|
| 172 |
|
|---|
| 173 |
def __unicode__(self): |
|---|
| 174 |
"Renders the field" |
|---|
| 175 |
return force_unicode(self.formfield.render(self.data)) |
|---|
| 176 |
|
|---|
| 177 |
def __repr__(self): |
|---|
| 178 |
return '<FormFieldWrapper for "%s">' % self.formfield.field_name |
|---|
| 179 |
|
|---|
| 180 |
def field_list(self): |
|---|
| 181 |
""" |
|---|
| 182 |
Like __str__(), but returns a list. Use this when the field's render() |
|---|
| 183 |
method returns a list. |
|---|
| 184 |
""" |
|---|
| 185 |
return self.formfield.render(self.data) |
|---|
| 186 |
|
|---|
| 187 |
def errors(self): |
|---|
| 188 |
return self.error_list |
|---|
| 189 |
|
|---|
| 190 |
def html_error_list(self): |
|---|
| 191 |
if self.errors(): |
|---|
| 192 |
return mark_safe('<ul class="errorlist"><li>%s</li></ul>' % '</li><li>'.join([escape(e) for e in self.errors()])) |
|---|
| 193 |
else: |
|---|
| 194 |
return mark_safe('') |
|---|
| 195 |
|
|---|
| 196 |
def get_id(self): |
|---|
| 197 |
return self.formfield.get_id() |
|---|
| 198 |
|
|---|
| 199 |
class FormFieldCollection(FormFieldWrapper): |
|---|
| 200 |
"A utility class that gives the template access to a dict of FormFieldWrappers" |
|---|
| 201 |
def __init__(self, formfield_dict): |
|---|
| 202 |
self.formfield_dict = formfield_dict |
|---|
| 203 |
|
|---|
| 204 |
def __str__(self): |
|---|
| 205 |
return unicode(self).encode('utf-8') |
|---|
| 206 |
|
|---|
| 207 |
def __unicode__(self): |
|---|
| 208 |
return unicode(self.formfield_dict) |
|---|
| 209 |
|
|---|
| 210 |
def __getitem__(self, template_key): |
|---|
| 211 |
"Look up field by template key; raise KeyError on failure" |
|---|
| 212 |
return self.formfield_dict[template_key] |
|---|
| 213 |
|
|---|
| 214 |
def __repr__(self): |
|---|
| 215 |
return "<FormFieldCollection: %s>" % self.formfield_dict |
|---|
| 216 |
|
|---|
| 217 |
def errors(self): |
|---|
| 218 |
"Returns list of all errors in this collection's formfields" |
|---|
| 219 |
errors = [] |
|---|
| 220 |
for field in self.formfield_dict.values(): |
|---|
| 221 |
if hasattr(field, 'errors'): |
|---|
| 222 |
errors.extend(field.errors()) |
|---|
| 223 |
return errors |
|---|
| 224 |
|
|---|
| 225 |
def has_errors(self): |
|---|
| 226 |
return bool(len(self.errors())) |
|---|
| 227 |
|
|---|
| 228 |
def html_combined_error_list(self): |
|---|
| 229 |
return mark_safe(''.join([field.html_error_list() for field in self.formfield_dict.values() if hasattr(field, 'errors')])) |
|---|
| 230 |
|
|---|
| 231 |
class InlineObjectCollection(object): |
|---|
| 232 |
"An object that acts like a sparse list of form field collections." |
|---|
| 233 |
def __init__(self, parent_manipulator, rel_obj, data, errors): |
|---|
| 234 |
self.parent_manipulator = parent_manipulator |
|---|
| 235 |
self.rel_obj = rel_obj |
|---|
| 236 |
self.data = data |
|---|
| 237 |
self.errors = errors |
|---|
| 238 |
self._collections = None |
|---|
| 239 |
self.name = rel_obj.name |
|---|
| 240 |
# This is the name used prior to fixing #1839. Needs for backwards |
|---|
| 241 |
# compatibility. |
|---|
| 242 |
self.orig_name = rel_obj.opts.module_name |
|---|
| 243 |
|
|---|
| 244 |
def __len__(self): |
|---|
| 245 |
self.fill() |
|---|
| 246 |
return self._collections.__len__() |
|---|
| 247 |
|
|---|
| 248 |
def __getitem__(self, k): |
|---|
| 249 |
self.fill() |
|---|
| 250 |
return self._collections.__getitem__(k) |
|---|
| 251 |
|
|---|
| 252 |
def __setitem__(self, k, v): |
|---|
| 253 |
self.fill() |
|---|
| 254 |
return self._collections.__setitem__(k,v) |
|---|
| 255 |
|
|---|
| 256 |
def __delitem__(self, k): |
|---|
| 257 |
self.fill() |
|---|
| 258 |
return self._collections.__delitem__(k) |
|---|
| 259 |
|
|---|
| 260 |
def __iter__(self): |
|---|
| 261 |
self.fill() |
|---|
| 262 |
return iter(self._collections.values()) |
|---|
| 263 |
|
|---|
| 264 |
def items(self): |
|---|
| 265 |
self.fill() |
|---|
| 266 |
return self._collections.items() |
|---|
| 267 |
|
|---|
| 268 |
def fill(self): |
|---|
| 269 |
if self._collections: |
|---|
| 270 |
return |
|---|
| 271 |
else: |
|---|
| 272 |
var_name = self.rel_obj.opts.object_name.lower() |
|---|
| 273 |
collections = {} |
|---|
| 274 |
orig = None |
|---|
| 275 |
if hasattr(self.parent_manipulator, 'original_object'): |
|---|
| 276 |
orig = self.parent_manipulator.original_object |
|---|
| 277 |
orig_list = self.rel_obj.get_list(orig) |
|---|
| 278 |
|
|---|
| 279 |
for i, instance in enumerate(orig_list): |
|---|
| 280 |
collection = {'original': instance} |
|---|
| 281 |
for f in self.rel_obj.editable_fields(): |
|---|
| 282 |
for field_name in f.get_manipulator_field_names(''): |
|---|
| 283 |
full_field_name = '%s.%d.%s' % (var_name, i, field_name) |
|---|
| 284 |
field = self.parent_manipulator[full_field_name] |
|---|
| 285 |
data = field.extract_data(self.data) |
|---|
| 286 |
errors = self.errors.get(full_field_name, []) |
|---|
| 287 |
collection[field_name] = FormFieldWrapper(field, data, errors) |
|---|
| 288 |
collections[i] = FormFieldCollection(collection) |
|---|
| 289 |
self._collections = collections |
|---|
| 290 |
|
|---|
| 291 |
|
|---|
| 292 |
class FormField(object): |
|---|
| 293 |
"""Abstract class representing a form field. |
|---|
| 294 |
|
|---|
| 295 |
Classes that extend FormField should define the following attributes: |
|---|
| 296 |
field_name |
|---|
| 297 |
The field's name for use by programs. |
|---|
| 298 |
validator_list |
|---|
| 299 |
A list of validation tests (callback functions) that the data for |
|---|
| 300 |
this field must pass in order to be added or changed. |
|---|
| 301 |
is_required |
|---|
| 302 |
A Boolean. Is it a required field? |
|---|
| 303 |
Subclasses should also implement a render(data) method, which is responsible |
|---|
| 304 |
for rending the form field in XHTML. |
|---|
| 305 |
""" |
|---|
| 306 |
|
|---|
| 307 |
def __str__(self): |
|---|
| 308 |
return unicode(self).encode('utf-8') |
|---|
| 309 |
|
|---|
| 310 |
def __unicode__(self): |
|---|
| 311 |
return self.render(u'') |
|---|
| 312 |
|
|---|
| 313 |
def __repr__(self): |
|---|
| 314 |
return 'FormField "%s"' % self.field_name |
|---|
| 315 |
|
|---|
| 316 |
def prepare(self, new_data): |
|---|
| 317 |
"Hook for doing something to new_data (in place) before validation." |
|---|
| 318 |
pass |
|---|
| 319 |
|
|---|
| 320 |
def html2python(data): |
|---|
| 321 |
"Hook for converting an HTML datatype (e.g. 'on' for checkboxes) to a Python type" |
|---|
| 322 |
return data |
|---|
| 323 |
html2python = staticmethod(html2python) |
|---|
| 324 |
|
|---|
| 325 |
def render(self, data): |
|---|
| 326 |
raise NotImplementedError |
|---|
| 327 |
|
|---|
| 328 |
def get_member_name(self): |
|---|
| 329 |
if hasattr(self, 'member_name'): |
|---|
| 330 |
return self.member_name |
|---|
| 331 |
else: |
|---|
| 332 |
return self.field_name |
|---|
| 333 |
|
|---|
| 334 |
def extract_data(self, data_dict): |
|---|
| 335 |
if hasattr(self, 'requires_data_list') and hasattr(data_dict, 'getlist'): |
|---|
| 336 |
data = data_dict.getlist(self.get_member_name()) |
|---|
| 337 |
else: |
|---|
| 338 |
data = data_dict.get(self.get_member_name(), None) |
|---|
| 339 |
if data is None: |
|---|
| 340 |
data = '' |
|---|
| 341 |
return data |
|---|
| 342 |
|
|---|
| 343 |
def convert_post_data(self, new_data): |
|---|
| 344 |
name = self.get_member_name() |
|---|
| 345 |
if self.field_name in new_data: |
|---|
| 346 |
d = new_data.getlist(self.field_name) |
|---|
| 347 |
try: |
|---|
| 348 |
converted_data = [self.__class__.html2python(data) for data in d] |
|---|
| 349 |
except ValueError: |
|---|
| 350 |
converted_data = d |
|---|
| 351 |
new_data.setlist(name, converted_data) |
|---|
| 352 |
else: |
|---|
| 353 |
try: |
|---|
| 354 |
#individual fields deal with None values themselves |
|---|
| 355 |
new_data.setlist(name, [self.__class__.html2python(None)]) |
|---|
| 356 |
except EmptyValue: |
|---|
| 357 |
new_data.setlist(name, []) |
|---|
| 358 |
|
|---|
| 359 |
|
|---|
| 360 |
def run_validator(self, new_data, validator): |
|---|
| 361 |
if self.is_required or new_data.get(self.field_name, False) or hasattr(validator, 'always_test'): |
|---|
| 362 |
if hasattr(self, 'requires_data_list'): |
|---|
| 363 |
validator(new_data.getlist(self.field_name), new_data) |
|---|
| 364 |
else: |
|---|
| 365 |
validator(new_data.get(self.field_name, ''), new_data) |
|---|
| 366 |
|
|---|
| 367 |
def get_validation_errors(self, new_data): |
|---|
| 368 |
errors = {} |
|---|
| 369 |
if self.is_required and not new_data.get(self.field_name, False): |
|---|
| 370 |
errors.setdefault(self.field_name, []).append(ugettext('This field is required.')) |
|---|
| 371 |
return errors |
|---|
| 372 |
try: |
|---|
| 373 |
for validator in self.validator_list: |
|---|
| 374 |
try: |
|---|
| 375 |
self.run_validator(new_data, validator) |
|---|
| 376 |
except validators.ValidationError, e: |
|---|
| 377 |
errors.setdefault(self.field_name, []).extend(e.messages) |
|---|
| 378 |
# If a CriticalValidationError is raised, ignore any other ValidationErrors |
|---|
| 379 |
# for this particular field |
|---|
| 380 |
except validators.CriticalValidationError, e: |
|---|
| 381 |
errors.setdefault(self.field_name, []).extend(e.messages) |
|---|
| 382 |
return errors |
|---|
| 383 |
|
|---|
| 384 |
def get_id(self): |
|---|
| 385 |
"Returns the HTML 'id' attribute for this form field." |
|---|
| 386 |
return FORM_FIELD_ID_PREFIX + self.field_name |
|---|
| 387 |
|
|---|
| 388 |
#################### |
|---|
| 389 |
# GENERIC WIDGETS # |
|---|
| 390 |
#################### |
|---|
| 391 |
|
|---|
| 392 |
class TextField(FormField): |
|---|
| 393 |
input_type = "text" |
|---|
| 394 |
def __init__(self, field_name, length=30, max_length=None, is_required=False, validator_list=None, member_name=None): |
|---|
| 395 |
if validator_list is None: validator_list = [] |
|---|
| 396 |
self.field_name = field_name |
|---|
| 397 |
self.length, self.max_length = length, max_length |
|---|
| 398 |
self.is_required = is_required |
|---|
| 399 |
self.validator_list = [self.isValidLength, self.hasNoNewlines] + validator_list |
|---|
| 400 |
if member_name != None: |
|---|
| 401 |
self.member_name = member_name |
|---|
| 402 |
|
|---|
| 403 |
def isValidLength(self, data, form): |
|---|
| 404 |
if data and self.max_length and len(smart_unicode(data)) > self.max_length: |
|---|
| 405 |
raise validators.ValidationError, ungettext("Ensure your text is less than %s character.", |
|---|
| 406 |
"Ensure your text is less than %s characters.", self.max_length) % self.max_length |
|---|
| 407 |
|
|---|
| 408 |
def hasNoNewlines(self, data, form): |
|---|
| 409 |
if data and '\n' in data: |
|---|
| 410 |
raise validators.ValidationError, ugettext("Line breaks are not allowed here.") |
|---|
| 411 |
|
|---|
| 412 |
def render(self, data): |
|---|
| 413 |
if data is None: |
|---|
| 414 |
data = u'' |
|---|
| 415 |
max_length = u'' |
|---|
| 416 |
if self.max_length: |
|---|
| 417 |
max_length = u'maxlength="%s" ' % self.max_length |
|---|
| 418 |
return mark_safe(u'<input type="%s" id="%s" class="v%s%s" name="%s" size="%s" value="%s" %s/>' % \ |
|---|
| 419 |
(self.input_type, self.get_id(), self.__class__.__name__, self.is_required and u' required' or '', |
|---|
| 420 |
self.field_name, self.length, escape(data), max_length)) |
|---|
| 421 |
|
|---|
| 422 |
def html2python(data): |
|---|
| 423 |
return data |
|---|
| 424 |
html2python = staticmethod(html2python) |
|---|
| 425 |
|
|---|
| 426 |
class PasswordField(TextField): |
|---|
| 427 |
input_type = "password" |
|---|
| 428 |
|
|---|
| 429 |
class LargeTextField(TextField): |
|---|
| 430 |
def __init__(self, field_name, rows=10, cols=40, is_required=False, validator_list=None, max_length=None): |
|---|
| 431 |
if validator_list is None: validator_list = [] |
|---|
| 432 |
self.field_name = field_name |
|---|
| 433 |
self.rows, self.cols, self.is_required = rows, cols, is_required |
|---|
| 434 |
self.validator_list = validator_list[:] |
|---|
| 435 |
if max_length: |
|---|
| 436 |
self.validator_list.append(self.isValidLength) |
|---|
| 437 |
self.max_length = max_length |
|---|
| 438 |
|
|---|
| 439 |
def render(self, data): |
|---|
| 440 |
if data is None: |
|---|
| 441 |
data = '' |
|---|
| 442 |
return mark_safe(u'<textarea id="%s" class="v%s%s" name="%s" rows="%s" cols="%s">%s</textarea>' % \ |
|---|
| 443 |
(self.get_id(), self.__class__.__name__, self.is_required and u' required' or u'', |
|---|
| 444 |
self.field_name, self.rows, self.cols, escape(data))) |
|---|
| 445 |
|
|---|
| 446 |
class HiddenField(FormField): |
|---|
| 447 |
def __init__(self, field_name, is_required=False, validator_list=None, max_length=None): |
|---|
| 448 |
if validator_list is None: validator_list = [] |
|---|
| 449 |
self.field_name, self.is_required = field_name, is_required |
|---|
| 450 |
self.validator_list = validator_list[:] |
|---|
| 451 |
|
|---|
| 452 |
def render(self, data): |
|---|
| 453 |
return mark_safe(u'<input type="hidden" id="%s" name="%s" value="%s" />' % \ |
|---|
| 454 |
(self.get_id(), self.field_name, escape(data))) |
|---|
| 455 |
|
|---|
| 456 |
class CheckboxField(FormField): |
|---|
| 457 |
def __init__(self, field_name, checked_by_default=False, validator_list=None, is_required=False): |
|---|
| 458 |
if validator_list is None: validator_list = [] |
|---|
| 459 |
self.field_name = field_name |
|---|
| 460 |
self.checked_by_default = checked_by_default |
|---|
| 461 |
self.is_required = is_required |
|---|
| 462 |
self.validator_list = validator_list[:] |
|---|
| 463 |
|
|---|
| 464 |
def render(self, data): |
|---|
| 465 |
checked_html = '' |
|---|
| 466 |
if data or (data is '' and self.checked_by_default): |
|---|
| 467 |
checked_html = ' checked="checked"' |
|---|
| 468 |
return mark_safe(u'<input type="checkbox" id="%s" class="v%s" name="%s"%s />' % \ |
|---|
| 469 |
(self.get_id(), self.__class__.__name__, |
|---|
| 470 |
self.field_name, checked_html)) |
|---|
| 471 |
|
|---|
| 472 |
def html2python(data): |
|---|
| 473 |
"Convert value from browser ('on' or '') to a Python boolean" |
|---|
| 474 |
if data == 'on': |
|---|
| 475 |
return True |
|---|
| 476 |
return False |
|---|
| 477 |
html2python = staticmethod(html2python) |
|---|
| 478 |
|
|---|
| 479 |
class SelectField(FormField): |
|---|
| 480 |
def __init__(self, field_name, choices=None, size=1, is_required=False, validator_list=None, member_name=None): |
|---|
| 481 |
if validator_list is None: validator_list = [] |
|---|
| 482 |
if choices is None: choices = [] |
|---|
| 483 |
choices = [(k, smart_unicode(v, strings_only=True)) for k, v in choices] |
|---|
| 484 |
self.field_name = field_name |
|---|
| 485 |
# choices is a list of (value, human-readable key) tuples because order matters |
|---|
| 486 |
self.choices, self.size, self.is_required = choices, size, is_required |
|---|
| 487 |
self.validator_list = [self.isValidChoice] + validator_list |
|---|
| 488 |
if member_name != None: |
|---|
| 489 |
self.member_name = member_name |
|---|
| 490 |
|
|---|
| 491 |
def render(self, data): |
|---|
| 492 |
output = [u'<select id="%s" class="v%s%s" name="%s" size="%s">' % \ |
|---|
| 493 |
(self.get_id(), self.__class__.__name__, |
|---|
| 494 |
self.is_required and u' required' or u'', self.field_name, self.size)] |
|---|
| 495 |
str_data = smart_unicode(data) # normalize to string |
|---|
| 496 |
for value, display_name in self |
|---|