"""
Tools for generating forms based on Django models.
"""
from wtforms import fields as f
from wtforms import Form
from wtforms import validators
from wtforms.compat import iteritems
from wtforms.ext.django.fields import ModelSelectField
__all__ = (
'model_fields', 'model_form',
)
class ModelConverterBase(object):
def __init__(self, converters):
self.converters = converters
def convert(self, model, field, field_args):
kwargs = {
'label': field.verbose_name,
'description': field.help_text,
'validators': [],
'filters': [],
'default': field.default,
}
if field_args:
kwargs.update(field_args)
if field.blank:
kwargs['validators'].append(validators.Optional())
if field.max_length is not None and field.max_length > 0:
kwargs['validators'].append(validators.Length(max=field.max_length))
ftype = type(field).__name__
if field.choices:
kwargs['choices'] = field.choices
return f.SelectField(**kwargs)
elif ftype in self.converters:
return self.converters[ftype](model, field, kwargs)
else:
converter = getattr(self, 'conv_%s' % ftype, None)
if converter is not None:
return converter(model, field, kwargs)
class ModelConverter(ModelConverterBase):
DEFAULT_SIMPLE_CONVERSIONS = {
f.IntegerField: ['AutoField', 'IntegerField', 'SmallIntegerField', 'PositiveIntegerField', 'PositiveSmallIntegerField'],
f.DecimalField: ['DecimalField', 'FloatField'],
f.FileField: ['FileField', 'FilePathField', 'ImageField'],
f.DateTimeField: ['DateTimeField'],
f.DateField: ['DateField'],
f.BooleanField: ['BooleanField'],
f.TextField: ['CharField', 'PhoneNumberField', 'SlugField'],
f.TextAreaField: ['TextField', 'XMLField'],
}
def __init__(self, extra_converters=None, simple_conversions=None):
converters = {}
if simple_conversions is None:
simple_conversions = self.DEFAULT_SIMPLE_CONVERSIONS
for field_type, django_fields in iteritems(simple_conversions):
converter = self.make_simple_converter(field_type)
for name in django_fields:
converters[name] = converter
if extra_converters:
converters.update(extra_converters)
super(ModelConverter, self).__init__(converters)
def make_simple_converter(self, field_type):
def _converter(model, field, kwargs):
return field_type(**kwargs)
return _converter
def conv_ForeignKey(self, model, field, kwargs):
return ModelSelectField(model=field.rel.to, **kwargs)
def conv_TimeField(self, model, field, kwargs):
def time_only(obj):
try:
return obj.time()
except AttributeError:
return obj
kwargs['filters'].append(time_only)
return f.DateTimeField(format='%H:%M:%S', **kwargs)
def conv_EmailField(self, model, field, kwargs):
kwargs['validators'].append(validators.email())
return f.TextField(**kwargs)
def conv_IPAddressField(self, model, field, kwargs):
kwargs['validators'].append(validators.ip_address())
return f.TextField(**kwargs)
def conv_URLField(self, model, field, kwargs):
kwargs['validators'].append(validators.url())
return f.TextField(**kwargs)
def conv_NullBooleanField(self, model, field, kwargs):
from django.db.models.fields import NOT_PROVIDED
def coerce_nullbool(value):
d = {'None': None, None: None, 'True': True, 'False': False}
if isinstance(value, NOT_PROVIDED):
return None
elif value in d:
return d[value]
else:
return bool(int(value))
choices = ((None, 'Unknown'), (True, 'Yes'), (False, 'No'))
return f.SelectField(choices=choices, coerce=coerce_nullbool, **kwargs)
def model_fields(model, only=None, exclude=None, field_args=None, converter=None):
"""
Generate a dictionary of fields for a given Django model.
See `model_form` docstring for description of parameters.
"""
converter = converter or ModelConverter()
field_args = field_args or {}
model_fields = ((f.attname, f) for f in model._meta.fields)
if only:
model_fields = (x for x in model_fields if x[0] in only)
elif exclude:
model_fields = (x for x in model_fields if x[0] not in exclude)
field_dict = {}
for name, model_field in model_fields:
field = converter.convert(model, model_field, field_args.get(name))
if field is not None:
field_dict[name] = field
return field_dict