Created
May 18, 2012 16:18
-
-
Save mentat/2726179 to your computer and use it in GitHub Desktop.
WTForm protorpc.Message converter/creator.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from protorpc import messages | |
from wtforms import Form, validators, widgets, fields as f | |
__all__ = ['pb_form'] | |
class ListPropertyField(f.TextAreaField): | |
""" | |
A field for any repeated property. The list items are rendered in a | |
textarea. | |
""" | |
def __init__(self, type_, **kwargs): | |
super(ListPropertyField, self).__init__(**kwargs) | |
self._type = type_ | |
def _value(self): | |
if self.raw_data: | |
return self.raw_data[0] | |
else: | |
return self.data and unicode(u"\n".join(filter(unicode, self.data))) or u'' | |
def process_formdata(self, valuelist): | |
if valuelist: | |
try: | |
self.data = [self._type(x) for x in valuelist[0].splitlines()] | |
except ValueError: | |
raise ValueError(self.gettext(u'Not a valid list')) | |
def get_TextField(kwargs): | |
""" | |
Returns a ``TextField``, applying the ``db.StringProperty`` length limit | |
of 500 bytes. | |
""" | |
kwargs['validators'].append(validators.length(max=500)) | |
return f.TextField(**kwargs) | |
def get_IntegerField(kwargs): | |
""" | |
Returns an ``IntegerField``, applying the ``db.IntegerProperty`` range | |
limits. | |
""" | |
v = validators.NumberRange(min=-0x8000000000000000, max=0x7fffffffffffffff) | |
kwargs['validators'].append(v) | |
return f.IntegerField(**kwargs) | |
def convert_StringProperty(pb, prop, kwargs): | |
"""Returns a form field for a ``protorpc.StringField``.""" | |
if prop.repeated: | |
return ListPropertyField(unicode, **kwargs) | |
else: | |
return get_TextField(kwargs) | |
def convert_BooleanProperty(pb, prop, kwargs): | |
"""Returns a form field for a ``protorpc.BooleanField``.""" | |
if prop.repeated: | |
return ListPropertyField(bool, **kwargs) | |
return f.BooleanField(**kwargs) | |
def convert_IntegerProperty(pb, prop, kwargs): | |
"""Returns a form field for a ``protorpc.IntegerField``.""" | |
if prop.repeated: | |
return ListPropertyField(long, **kwargs) | |
return f.IntegerField(**kwargs) | |
def convert_FloatProperty(pb, prop, kwargs): | |
"""Returns a form field for a ``protorpc.FloatField``.""" | |
if prop.repeated: | |
return ListPropertyField(float, **kwargs) | |
return f.FloatField(**kwargs) | |
def convert_MessageProperty(pb, prop, kwargs): | |
"""Returns a form field for a ``protorpc.MesssageField``.""" | |
if prop.repeated: | |
return f.FieldList(f.FormField(pb_form(prop.type), **kwargs), min_entries=1) | |
return f.FormField(pb_form(prop.type), **kwargs) | |
def convert_BlobProperty(pb, prop, kwargs): | |
"""Returns a form field for a ``protorpc.BytesField``.""" | |
return f.FileField(**kwargs) | |
class PBConverter(object): | |
""" | |
Converts properties from a ``protorpc.Message`` class to form fields. | |
Default conversions between properties and fields: | |
+====================+===================+==============+==================+ | |
| Property subclass | Field subclass | datatype | notes | | |
+====================+===================+==============+==================+ | |
| StringField | TextField | unicode | | | |
+--------------------+-------------------+--------------+------------------+ | |
| BytesField | FileField | byte | | | |
+--------------------+-------------------+--------------+------------------+ | |
| BooleanField | BooleanField | bool | | | |
+--------------------+-------------------+--------------+------------------+ | |
| IntegerField | IntegerField | int or long | | | |
+--------------------+-------------------+--------------+------------------+ | |
| FloatField | TextField | float | | | |
+--------------------+-------------------+--------------+------------------+ | |
| MessageField | FormField | dict | Use DictLSP | | |
+====================+===================+==============+==================+ | |
* https://gist.github.com/40a5ce996aed99a49432 | |
""" | |
default_converters = { | |
'StringField': convert_StringProperty, | |
'BytesField': convert_BlobProperty, | |
'BooleanField': convert_BooleanProperty, | |
'IntegerField': convert_IntegerProperty, | |
'FloatField': convert_FloatProperty, | |
'MessageField': convert_MessageProperty, | |
'EnumField': convert_IntegerProperty, | |
} | |
def __init__(self, converters=None): | |
""" | |
Constructs the converter, setting the converter callables. | |
:param converters: | |
A dictionary of converter callables for each property type. The | |
callable must accept the arguments (pb, prop, kwargs). | |
""" | |
self.converters = converters or self.default_converters | |
def convert(self, pb, field, field_args): | |
""" | |
Returns a form field for a single model property. | |
:param pb: | |
The ``protorpc.Message`` class that contains the property. | |
:param field: | |
The message field: a ``protorpc.Field`` instance. | |
:param field_args: | |
Optional keyword arguments to construct the field. | |
""" | |
kwargs = { | |
'label': field.name.replace('_', ' ').title(), | |
'default': field.default, | |
'validators': [], | |
} | |
if field_args: | |
kwargs.update(field_args) | |
if field.required: | |
kwargs['validators'].append(validators.required()) | |
if isinstance(field, messages.EnumField): | |
# Use choices in a select field. | |
kwargs['choices'] = [(v, k.replace('_', ' ')) for k,v in field.type.to_dict().iteritems()] | |
kwargs['coerce'] = int | |
kwargs['default'] = 1 | |
return f.SelectField(**kwargs) | |
else: | |
converter = self.converters.get(type(field).__name__, None) | |
if converter is not None: | |
return converter(pb, field, kwargs) | |
def pb_fields(pb, only=None, exclude=None, field_args=None, | |
converter=None): | |
""" | |
Extracts and returns a dictionary of form fields for a given | |
``protorpc.Message`` class. | |
:param pb: | |
The ``protorpc.Message`` class to extract fields from. | |
:param only: | |
An optional iterable with the property names that should be included in | |
the form. Only these properties will have fields. | |
:param exclude: | |
An optional iterable with the property names that should be excluded | |
from the form. All other properties will have fields. | |
:param field_args: | |
An optional dictionary of field names mapping to a keyword arguments | |
used to construct each field object. | |
:param converter: | |
A converter to generate the fields based on the model properties. If | |
not set, ``PBConverter`` is used. | |
""" | |
converter = converter or PBConverter() | |
field_args = field_args or {} | |
# Get the field names we want to include or exclude, starting with the | |
# full list of model properties. | |
props = pb._Message__by_name | |
field_names = list(pb._Message__by_name.iterkeys()) | |
if only: | |
field_names = list(f for f in only if f in field_names) | |
elif exclude: | |
field_names = list(f for f in field_names if f not in exclude) | |
# Create all fields. | |
field_dict = {} | |
for name in field_names: | |
field = converter.convert(pb, props[name], field_args.get(name)) | |
if field is not None: | |
field_dict[name] = field | |
return field_dict | |
def pb_form(pb, base_class=Form, only=None, exclude=None, field_args=None, | |
converter=None): | |
""" | |
Creates and returns a dynamic ``wtforms.Form`` class for a given | |
``protorpc.Message`` class. The form class can be used as it is or serve as a base | |
for extended form classes, which can then mix non-model related fields, | |
subforms with other model forms, among other possibilities. | |
:param pb: | |
The ``protorpc.Message`` class to generate a form for. | |
:param base_class: | |
Base form class to extend from. Must be a ``wtforms.Form`` subclass. | |
:param only: | |
An optional iterable with the property names that should be included in | |
the form. Only these properties will have fields. | |
:param exclude: | |
An optional iterable with the property names that should be excluded | |
from the form. All other properties will have fields. | |
:param field_args: | |
An optional dictionary of field names mapping to keyword arguments | |
used to construct each field object. | |
:param converter: | |
A converter to generate the fields based on the model properties. If | |
not set, ``PBConverter`` is used. | |
""" | |
# Extract the fields from the model. | |
field_dict = pb_fields(pb, only, exclude, field_args, converter) | |
# Return a dynamically created form class, extending from base_class and | |
# including the created fields as properties. | |
return type(type(pb).__name__ + 'Form', (base_class,), field_dict) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment