Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

init

  • Loading branch information...
commit c47ec9941f8f572c48d72d9baac88b180a9e851e 0 parents
Hsiaoming Yang authored
11 .gitignore
... ... @@ -0,0 +1,11 @@
  1 +*.pyc
  2 +*.pyo
  3 +*.egg-info
  4 +bin
  5 +build
  6 +develop-eggs
  7 +dist
  8 +eggs
  9 +parts
  10 +.DS_Store
  11 +.installed.cfg
0  tforms/__init__.py
No changes.
577 tforms/core.py
... ... @@ -0,0 +1,577 @@
  1 +
  2 +from tornado.escape import to_unicode, utf8, xhtml_escape
  3 +
  4 +class BaseForm(object):
  5 + """
  6 + Base Form Class. Provides core behaviour like field construction,
  7 + validation, and data and error proxying.
  8 + """
  9 +
  10 + def __init__(self, fields, prefix=''):
  11 + """
  12 + :param fields:
  13 + A dict or sequence of 2-tuples of partially-constructed fields.
  14 + :param prefix:
  15 + If provided, all fields will have their name prefixed with the
  16 + value.
  17 + """
  18 + if prefix and prefix[-1] not in '-_;:/.':
  19 + prefix += '-'
  20 +
  21 + self._prefix = prefix
  22 + self._errors = None
  23 + self._fields = {}
  24 +
  25 + if hasattr(fields, 'iteritems'):
  26 + fields = fields.iteritems()
  27 +
  28 + locale = self._get_locale()
  29 +
  30 + for name, unbound_field in fields:
  31 + field = unbound_field.bind(form=self, name=name, prefix=prefix, locale=locale)
  32 + self._fields[name] = field
  33 +
  34 + def __iter__(self):
  35 + """ Iterate form fields in arbitrary order """
  36 + return self._fields.itervalues()
  37 +
  38 + def __contains__(self, item):
  39 + """ Returns `True` if the named field is a member of this form. """
  40 + return (item in self._fields)
  41 +
  42 + def __getitem__(self, name):
  43 + """ Dict-style access to this form's fields."""
  44 + return self._fields[name]
  45 +
  46 + def __setitem__(self, name, value):
  47 + """ Bind a field to this form. """
  48 + self._fields[name] = value.bind(form=self, name=name, prefix=self._prefix)
  49 +
  50 + def __delitem__(self, name):
  51 + """ Remove a field from this form. """
  52 + del self._fields[name]
  53 +
  54 + def _get_locale(self):
  55 + """
  56 + Override in subclasses to provide alternate translations factory.
  57 +
  58 + Must return an object that provides gettext() and ngettext() methods.
  59 + """
  60 + return None
  61 +
  62 + def populate_obj(self, obj):
  63 + """
  64 + Populates the attributes of the passed `obj` with data from the form's
  65 + fields.
  66 +
  67 + :note: This is a destructive operation; Any attribute with the same name
  68 + as a field will be overridden. Use with caution.
  69 + """
  70 + for name, field in self._fields.iteritems():
  71 + field.populate_obj(obj, name)
  72 +
  73 + def process(self, formdata=None, obj=None, **kwargs):
  74 + """
  75 + Take form, object data, and keyword arg input and have the fields
  76 + process them.
  77 +
  78 + :param formdata:
  79 + Used to pass data coming from the enduser, usually `request.POST` or
  80 + equivalent.
  81 + :param obj:
  82 + If `formdata` has no data for a field, the form will try to get it
  83 + from the passed object.
  84 + :param `**kwargs`:
  85 + If neither `formdata` or `obj` contains a value for a field, the
  86 + form will assign the value of a matching keyword argument to the
  87 + field, if provided.
  88 + """
  89 + if formdata is not None and not hasattr(formdata, 'getlist'):
  90 + if hasattr(formdata, 'getall'):
  91 + formdata = WebobInputWrapper(formdata)
  92 + else:
  93 + raise TypeError("formdata should be a multidict-type wrapper that supports the 'getlist' method")
  94 +
  95 + for name, field, in self._fields.iteritems():
  96 + if obj is not None and hasattr(obj, name):
  97 + field.process(formdata, getattr(obj, name))
  98 + elif name in kwargs:
  99 + field.process(formdata, kwargs[name])
  100 + else:
  101 + field.process(formdata)
  102 +
  103 + def validate(self, extra_validators=None):
  104 + """
  105 + Validates the form by calling `validate` on each field.
  106 +
  107 + :param extra_validators:
  108 + If provided, is a dict mapping field names to a sequence of
  109 + callables which will be passed as extra validators to the field's
  110 + `validate` method.
  111 +
  112 + Returns `True` if no errors occur.
  113 + """
  114 + self._errors = None
  115 + success = True
  116 + for name, field in self._fields.iteritems():
  117 + if extra_validators is not None and name in extra_validators:
  118 + extra = extra_validators[name]
  119 + else:
  120 + extra = tuple()
  121 + if not field.validate(self, extra):
  122 + success = False
  123 + return success
  124 +
  125 + @property
  126 + def data(self):
  127 + return dict((name, f.data) for name, f in self._fields.iteritems())
  128 +
  129 + @property
  130 + def errors(self):
  131 + if self._errors is None:
  132 + self._errors = dict((name, f.errors) for name, f in self._fields.iteritems() if f.errors)
  133 + return self._errors
  134 +
  135 +class FormMeta(type):
  136 + """
  137 + The metaclass for `Form` and any subclasses of `Form`.
  138 +
  139 + `FormMeta`'s responsibility is to create the `_unbound_fields` list, which
  140 + is a list of `UnboundField` instances sorted by their order of
  141 + instantiation. The list is created at the first instantiation of the form.
  142 + If any fields are added/removed from the form, the list is cleared to be
  143 + re-generated on the next instantiaton.
  144 +
  145 + Any properties which begin with an underscore or are not `UnboundField`
  146 + instances are ignored by the metaclass.
  147 + """
  148 + def __init__(cls, name, bases, attrs):
  149 + type.__init__(cls, name, bases, attrs)
  150 + cls._unbound_fields = None
  151 +
  152 + def __call__(cls, *args, **kwargs):
  153 + """
  154 + Construct a new `Form` instance, creating `_unbound_fields` on the
  155 + class if it is empty.
  156 + """
  157 + if cls._unbound_fields is None:
  158 + fields = []
  159 + for name in dir(cls):
  160 + if not name.startswith('_'):
  161 + unbound_field = getattr(cls, name)
  162 + if hasattr(unbound_field, '_formfield'):
  163 + fields.append((name, unbound_field))
  164 + # We keep the name as the second element of the sort
  165 + # to ensure a stable sort.
  166 + fields.sort(key=lambda x: (x[1].creation_counter, x[0]))
  167 + cls._unbound_fields = fields
  168 + return type.__call__(cls, *args, **kwargs)
  169 +
  170 + def __setattr__(cls, name, value):
  171 + """
  172 + Add an attribute to the class, clearing `_unbound_fields` if needed.
  173 + """
  174 + if not name.startswith('_') and hasattr(value, '_formfield'):
  175 + cls._unbound_fields = None
  176 + type.__setattr__(cls, name, value)
  177 +
  178 + def __delattr__(cls, name):
  179 + """
  180 + Remove an attribute from the class, clearing `_unbound_fields` if
  181 + needed.
  182 + """
  183 + if not name.startswith('_'):
  184 + cls._unbound_fields = None
  185 + type.__delattr__(cls, name)
  186 +
  187 +class Form(BaseForm):
  188 + """
  189 + Declarative Form base class. Extends BaseForm's core behaviour allowing
  190 + fields to be defined on Form subclasses as class attributes.
  191 +
  192 + In addition, form and instance input data are taken at construction time
  193 + and passed to `process()`.
  194 + """
  195 + __metaclass__ = FormMeta
  196 +
  197 + def __init__(self, formdata=None, obj=None, prefix='', **kwargs):
  198 + """
  199 + :param formdata:
  200 + Used to pass data coming from the enduser, usually `request.POST` or
  201 + equivalent.
  202 + :param obj:
  203 + If `formdata` has no data for a field, the form will try to get it
  204 + from the passed object.
  205 + :param prefix:
  206 + If provided, all fields will have their name prefixed with the
  207 + value.
  208 + :param `**kwargs`:
  209 + If neither `formdata` or `obj` contains a value for a field, the
  210 + form will assign the value of a matching keyword argument to the
  211 + field, if provided.
  212 + """
  213 + super(Form, self).__init__(self._unbound_fields, prefix=prefix)
  214 +
  215 + for name, field in self._fields.iteritems():
  216 + # Set all the fields to attributes so that they obscure the class
  217 + # attributes with the same names.
  218 + setattr(self, name, field)
  219 +
  220 + self.process(formdata, obj, **kwargs)
  221 + self.obj = obj
  222 +
  223 + def __iter__(self):
  224 + """ Iterate form fields in their order of definition on the form. """
  225 + for name, _ in self._unbound_fields:
  226 + if name in self._fields:
  227 + yield self._fields[name]
  228 +
  229 + def __setitem__(self, name, value):
  230 + raise TypeError('Fields may not be added to Form instances, only classes.')
  231 +
  232 + def __delitem__(self, name):
  233 + del self._fields[name]
  234 + setattr(self, name, None)
  235 +
  236 + def __delattr__(self, name):
  237 + try:
  238 + self.__delitem__(name)
  239 + except KeyError:
  240 + super(Form, self).__delattr__(name)
  241 +
  242 + def validate(self):
  243 + """
  244 + Validates the form by calling `validate` on each field, passing any
  245 + extra `Form.validate_<fieldname>` validators to the field validator.
  246 + """
  247 + extra = {}
  248 + for name in self._fields:
  249 + inline = getattr(self.__class__, 'validate_%s' % name, None)
  250 + if inline is not None:
  251 + extra[name] = [inline]
  252 +
  253 + return super(Form, self).validate(extra)
  254 +
  255 +
  256 +class DummyLocale(object):
  257 + def translate(self, message, plural_message=None, count=None):
  258 + if plural_message is not None:
  259 + assert count is not None
  260 + return plural_message
  261 + return message
  262 +
  263 +
  264 +_unset_value = object()
  265 +class Field(object):
  266 + """
  267 + Field base class
  268 + """
  269 + widget = None
  270 + errors = tuple()
  271 + process_errors = tuple()
  272 + _formfield = True
  273 + _locale = DummyLocale()
  274 +
  275 + def __new__(cls, *args, **kwargs):
  276 + if '_form' in kwargs and '_name' in kwargs:
  277 + return super(Field, cls).__new__(cls)
  278 + else:
  279 + return UnboundField(cls, *args, **kwargs)
  280 +
  281 + def __init__(self, label=None, validators=None, filters=tuple(),
  282 + description='', id=None, default=None, widget=None,
  283 + _form=None, _name=None, _prefix='', _locale=None):
  284 + """
  285 + Construct a new field.
  286 +
  287 + :param label:
  288 + The label of the field.
  289 + :param validators:
  290 + A sequence of validators to call when `validate` is called.
  291 + :param filters:
  292 + A sequence of filters which are run on input data by `process`.
  293 + :param description:
  294 + A description for the field, typically used for help text.
  295 + :param id:
  296 + An id to use for the field. A reasonable default is set by the form,
  297 + and you shouldn't need to set this manually.
  298 + :param default:
  299 + The default value to assign to the field, if no form or object
  300 + input is provided. May be a callable.
  301 + :param widget:
  302 + If provided, overrides the widget used to render the field.
  303 + :param _form:
  304 + The form holding this field. It is passed by the form itself during
  305 + construction. You should never pass this value yourself.
  306 + :param _name:
  307 + The name of this field, passed by the enclosing form during its
  308 + construction. You should never pass this value yourself.
  309 + :param _prefix:
  310 + The prefix to prepend to the form name of this field, passed by
  311 + the enclosing form during construction.
  312 +
  313 + If `_form` and `_name` isn't provided, an :class:`UnboundField` will be
  314 + returned instead. Call its :func:`bind` method with a form instance and
  315 + a name to construct the field.
  316 + """
  317 + self.short_name = _name
  318 + self.name = _prefix + _name
  319 + if _locale is not None:
  320 + self._locale = _locale
  321 + self.id = id or self.name
  322 + if label is None:
  323 + label = _name.replace('_', ' ').title()
  324 + self.label = Label(self.id, label)
  325 + if validators is None:
  326 + validators = []
  327 + self.validators = validators
  328 + self.filters = filters
  329 + self.description = description
  330 + self.type = type(self).__name__
  331 + self.default = default
  332 + self.raw_data = None
  333 + if widget:
  334 + self.widget = widget
  335 + self.flags = Flags()
  336 + for v in validators:
  337 + flags = getattr(v, 'field_flags', ())
  338 + for f in flags:
  339 + setattr(self.flags, f, True)
  340 +
  341 + def __unicode__(self):
  342 + """
  343 + Returns a HTML representation of the field. For more powerful rendering,
  344 + see the `__call__` method.
  345 + """
  346 + return self()
  347 +
  348 + def __str__(self):
  349 + """
  350 + Returns a HTML representation of the field. For more powerful rendering,
  351 + see the `__call__` method.
  352 + """
  353 + return self()
  354 +
  355 + def __html__(self):
  356 + """
  357 + Returns a HTML representation of the field. For more powerful rendering,
  358 + see the `__call__` method.
  359 + """
  360 + return self()
  361 +
  362 + def __call__(self, **kwargs):
  363 + """
  364 + Render this field as HTML, using keyword args as additional attributes.
  365 +
  366 + Any HTML attribute passed to the method will be added to the tag
  367 + and entity-escaped properly.
  368 + """
  369 + return self.widget(self, **kwargs)
  370 +
  371 + def translate(self, message, plural_message=None, count=None):
  372 + return self._locale.translate(message, plural_message, count)
  373 +
  374 + def validate(self, form, extra_validators=tuple()):
  375 + """
  376 + Validates the field and returns True or False. `self.errors` will
  377 + contain any errors raised during validation. This is usually only
  378 + called by `Form.validate`.
  379 +
  380 + Subfields shouldn't override this, but rather override either
  381 + `pre_validate`, `post_validate` or both, depending on needs.
  382 +
  383 + :param form: The form the field belongs to.
  384 + :param extra_validators: A list of extra validators to run.
  385 + """
  386 + self.errors = list(self.process_errors)
  387 + stop_validation = False
  388 +
  389 + # Call pre_validate
  390 + try:
  391 + self.pre_validate(form)
  392 + except StopValidation as e:
  393 + if e.args and e.args[0]:
  394 + self.errors.append(e.args[0])
  395 + stop_validation = True
  396 + except ValueError as e:
  397 + self.errors.append(e.args[0])
  398 +
  399 + # Run validators
  400 + if not stop_validation:
  401 + for validator in itertools.chain(self.validators, extra_validators):
  402 + try:
  403 + validator(form, self)
  404 + except StopValidation as e:
  405 + if e.args and e.args[0]:
  406 + self.errors.append(e.args[0])
  407 + stop_validation = True
  408 + break
  409 + except ValueError as e:
  410 + self.errors.append(e.args[0])
  411 +
  412 + # Call post_validate
  413 + try:
  414 + self.post_validate(form, stop_validation)
  415 + except ValueError as e:
  416 + self.errors.append(e.args[0])
  417 +
  418 + return len(self.errors) == 0
  419 +
  420 + def pre_validate(self, form):
  421 + """
  422 + Override if you need field-level validation. Runs before any other
  423 + validators.
  424 +
  425 + :param form: The form the field belongs to.
  426 + """
  427 + pass
  428 +
  429 + def post_validate(self, form, validation_stopped):
  430 + """
  431 + Override if you need to run any field-level validation tasks after
  432 + normal validation. This shouldn't be needed in most cases.
  433 +
  434 + :param form: The form the field belongs to.
  435 + :param validation_stopped:
  436 + `True` if any validator raised StopValidation.
  437 + """
  438 + pass
  439 +
  440 + def process(self, formdata, data=_unset_value):
  441 + """
  442 + Process incoming data, calling process_data, process_formdata as needed,
  443 + and run filters.
  444 +
  445 + If `data` is not provided, process_data will be called on the field's
  446 + default.
  447 +
  448 + Field subclasses usually won't override this, instead overriding the
  449 + process_formdata and process_data methods. Only override this for
  450 + special advanced processing, such as when a field encapsulates many
  451 + inputs.
  452 + """
  453 + self.process_errors = []
  454 + if data is _unset_value:
  455 + try:
  456 + data = self.default()
  457 + except TypeError:
  458 + data = self.default
  459 + try:
  460 + self.process_data(data)
  461 + except ValueError as e:
  462 + self.process_errors.append(e.args[0])
  463 +
  464 + if formdata:
  465 + try:
  466 + if self.name in formdata:
  467 + self.raw_data = formdata.getlist(self.name)
  468 + else:
  469 + self.raw_data = []
  470 + self.process_formdata(self.raw_data)
  471 + except ValueError as e:
  472 + self.process_errors.append(e.args[0])
  473 +
  474 + for filter in self.filters:
  475 + try:
  476 + self.data = filter(self.data)
  477 + except ValueError as e:
  478 + self.process_errors.append(e.args[0])
  479 +
  480 + def process_data(self, value):
  481 + """
  482 + Process the Python data applied to this field and store the result.
  483 +
  484 + This will be called during form construction by the form's `kwargs` or
  485 + `obj` argument.
  486 +
  487 + :param value: The python object containing the value to process.
  488 + """
  489 + self.data = value
  490 +
  491 + def process_formdata(self, valuelist):
  492 + """
  493 + Process data received over the wire from a form.
  494 +
  495 + This will be called during form construction with data supplied
  496 + through the `formdata` argument.
  497 +
  498 + :param valuelist: A list of strings to process.
  499 + """
  500 + if valuelist:
  501 + self.data = valuelist[0]
  502 +
  503 + def populate_obj(self, obj, name):
  504 + """
  505 + Populates `obj.<name>` with the field's data.
  506 +
  507 + :note: This is a destructive operation. If `obj.<name>` already exists,
  508 + it will be overridden. Use with caution.
  509 + """
  510 + setattr(obj, name, self.data)
  511 +
  512 +
  513 +class UnboundField(object):
  514 + _formfield = True
  515 + creation_counter = 0
  516 +
  517 + def __init__(self, field_class, *args, **kwargs):
  518 + UnboundField.creation_counter += 1
  519 + self.field_class = field_class
  520 + self.args = args
  521 + self.kwargs = kwargs
  522 + self.creation_counter = UnboundField.creation_counter
  523 +
  524 + def bind(self, form, name, prefix='', locale=None, **kwargs):
  525 + return self.field_class(_form=form, _prefix=prefix, _name=name, _locale=locale, *self.args, **dict(self.kwargs, **kwargs))
  526 +
  527 + def __repr__(self):
  528 + return '<UnboundField(%s, %r, %r)>' % (self.field_class.__name__, self.args, self.kwargs)
  529 +
  530 +def html_params(**kwargs):
  531 + """
  532 + Generate HTML parameters from inputted keyword arguments.
  533 +
  534 + The output value is sorted by the passed keys, to provide consistent output
  535 + each time this function is called with the same parameters. Because of the
  536 + frequent use of the normally reserved keywords `class` and `for`, suffixing
  537 + these with an underscore will allow them to be used.
  538 +
  539 + >>> html_params(name='text1', id='f', class_='text')
  540 + 'class="text" id="f" name="text1"'
  541 + """
  542 + params = []
  543 + for k,v in sorted(kwargs.iteritems()):
  544 + if k in ('class_', 'for_'):
  545 + k = k[:-1]
  546 + if v is True:
  547 + params.append(k)
  548 + else:
  549 + params.append('%s="%s"' % (to_unicode(k), xhtml_escape(to_unicode(v))))
  550 + return ' '.join(params)
  551 +
  552 +
  553 +class Label(object):
  554 + """
  555 + An HTML form label.
  556 + """
  557 + def __init__(self, field_id, text):
  558 + self.field_id = field_id
  559 + self.text = text
  560 +
  561 + def __str__(self):
  562 + return utf8(self())
  563 +
  564 + def __unicode__(self):
  565 + return self()
  566 +
  567 + def __html__(self):
  568 + return self()
  569 +
  570 + def __call__(self, text=None, **kwargs):
  571 + kwargs['for'] = self.field_id
  572 + attributes = html_params(**kwargs)
  573 + return '<label %s>%s</label>' % (attributes, to_unicode(text) or to_unicode(self.text))
  574 +
  575 + def __repr__(self):
  576 + return 'Label(%r, %r)' % (self.field_id, self.text)
  577 +

0 comments on commit c47ec99

Please sign in to comment.
Something went wrong with that request. Please try again.