# Source code for wtforms.ext.django.orm

    """
    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)