123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168 |
- """
- 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
- def model_form(model, base_class=Form, only=None, exclude=None, field_args=None, converter=None):
- """
- Create a wtforms Form for a given Django model class::
- from wtforms.ext.django.orm import model_form
- from myproject.myapp.models import User
- UserForm = model_form(User)
- :param model:
- A Django ORM model class
- :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, ``ModelConverter`` is used.
- """
- field_dict = model_fields(model, only, exclude, field_args, converter)
- return type(model._meta.object_name + 'Form', (base_class, ), field_dict)
|